From f215e02bf85f68d3a6106c2a1f4f7f063f819064 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:17:27 +0200 Subject: Adding upstream version 7.0.14-dfsg. Signed-off-by: Daniel Baumann --- src/VBox/Runtime/common/Makefile.kup | 0 src/VBox/Runtime/common/alloc/Makefile.kup | 0 src/VBox/Runtime/common/alloc/alloc.cpp | 83 + src/VBox/Runtime/common/alloc/heapoffset.cpp | 938 + src/VBox/Runtime/common/alloc/heapsimple.cpp | 930 + src/VBox/Runtime/common/alloc/memcache.cpp | 595 + src/VBox/Runtime/common/alloc/memtracker.cpp | 1359 + src/VBox/Runtime/common/asm/ASMAddFlags.asm | 80 + .../Runtime/common/asm/ASMAtomicCmpXchgExU64.asm | 94 + .../Runtime/common/asm/ASMAtomicCmpXchgU16.asm | 73 + .../Runtime/common/asm/ASMAtomicCmpXchgU64.asm | 88 + src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU8.asm | 73 + src/VBox/Runtime/common/asm/ASMAtomicReadU64.asm | 81 + src/VBox/Runtime/common/asm/ASMAtomicUoAndU32.asm | 66 + src/VBox/Runtime/common/asm/ASMAtomicUoAndU64.asm | 86 + src/VBox/Runtime/common/asm/ASMAtomicUoDecU32.asm | 66 + src/VBox/Runtime/common/asm/ASMAtomicUoIncU32.asm | 66 + src/VBox/Runtime/common/asm/ASMAtomicUoOrU32.asm | 66 + src/VBox/Runtime/common/asm/ASMAtomicUoOrU64.asm | 86 + src/VBox/Runtime/common/asm/ASMAtomicUoReadU64.asm | 80 + src/VBox/Runtime/common/asm/ASMAtomicUoXorU32.asm | 66 + src/VBox/Runtime/common/asm/ASMAtomicXchgU16.asm | 70 + src/VBox/Runtime/common/asm/ASMAtomicXchgU64.asm | 80 + .../common/asm/ASMBitFirstClear-generic.cpp | 107 + src/VBox/Runtime/common/asm/ASMBitFirstClear.asm | 137 + .../Runtime/common/asm/ASMBitFirstSet-generic.cpp | 107 + src/VBox/Runtime/common/asm/ASMBitFirstSet.asm | 137 + src/VBox/Runtime/common/asm/ASMBitFirstSetU16.asm | 103 + src/VBox/Runtime/common/asm/ASMBitFirstSetU32.asm | 107 + src/VBox/Runtime/common/asm/ASMBitFirstSetU64.asm | 126 + src/VBox/Runtime/common/asm/ASMBitLastSetU16.asm | 101 + src/VBox/Runtime/common/asm/ASMBitLastSetU32.asm | 107 + src/VBox/Runtime/common/asm/ASMBitLastSetU64.asm | 130 + .../Runtime/common/asm/ASMBitNextClear-generic.cpp | 79 + src/VBox/Runtime/common/asm/ASMBitNextClear.asm | 183 + .../Runtime/common/asm/ASMBitNextSet-generic.cpp | 79 + src/VBox/Runtime/common/asm/ASMBitNextSet.asm | 183 + src/VBox/Runtime/common/asm/ASMCpuId.asm | 121 + src/VBox/Runtime/common/asm/ASMCpuIdExSlow.asm | 181 + src/VBox/Runtime/common/asm/ASMCpuId_Idx_ECX.asm | 126 + src/VBox/Runtime/common/asm/ASMFxRstor.asm | 74 + src/VBox/Runtime/common/asm/ASMFxSave.asm | 74 + src/VBox/Runtime/common/asm/ASMGetFSBase.asm | 54 + src/VBox/Runtime/common/asm/ASMGetFlags.asm | 53 + src/VBox/Runtime/common/asm/ASMGetGDTR.asm | 62 + src/VBox/Runtime/common/asm/ASMGetGSBase.asm | 54 + src/VBox/Runtime/common/asm/ASMGetIDTR.asm | 62 + src/VBox/Runtime/common/asm/ASMGetIdtrLimit.asm | 58 + src/VBox/Runtime/common/asm/ASMGetLDTR.asm | 53 + src/VBox/Runtime/common/asm/ASMGetSegAttr.asm | 71 + src/VBox/Runtime/common/asm/ASMGetTR.asm | 53 + src/VBox/Runtime/common/asm/ASMGetXcr0.asm | 66 + .../Runtime/common/asm/ASMMemFill32-generic.cpp | 74 + .../asm/ASMMemFirstMismatchingU8-generic.cpp | 55 + .../common/asm/ASMMemFirstMismatchingU8.asm | 355 + .../common/asm/ASMMemFirstNonZero-generic.cpp | 85 + .../Runtime/common/asm/ASMMemZero32-generic.cpp | 51 + .../Runtime/common/asm/ASMMemZeroPage-generic.cpp | 51 + .../Runtime/common/asm/ASMMultU32ByU32DivByU32.asm | 74 + .../common/asm/ASMMultU64ByU32DivByU32-generic.cpp | 56 + .../Runtime/common/asm/ASMMultU64ByU32DivByU32.asm | 141 + src/VBox/Runtime/common/asm/ASMNopPause.asm | 51 + src/VBox/Runtime/common/asm/ASMRdMsrEx.asm | 94 + .../common/asm/ASMSerializeInstruction-cpuid.asm | 59 + .../common/asm/ASMSerializeInstruction-iret.asm | 71 + .../common/asm/ASMSerializeInstruction-rdtscp.asm | 56 + src/VBox/Runtime/common/asm/ASMSetFSBase.asm | 58 + src/VBox/Runtime/common/asm/ASMSetFlags.asm | 69 + src/VBox/Runtime/common/asm/ASMSetGDTR.asm | 62 + src/VBox/Runtime/common/asm/ASMSetGSBase.asm | 58 + src/VBox/Runtime/common/asm/ASMSetIDTR.asm | 62 + src/VBox/Runtime/common/asm/ASMSetXcr0.asm | 80 + src/VBox/Runtime/common/asm/ASMWrMsr.asm | 99 + src/VBox/Runtime/common/asm/ASMWrMsrEx.asm | 89 + src/VBox/Runtime/common/asm/ASMXRstor.asm | 73 + src/VBox/Runtime/common/asm/ASMXSave.asm | 73 + src/VBox/Runtime/common/asm/Makefile.kup | 0 src/VBox/Runtime/common/asm/asm-fake.cpp | 354 + src/VBox/Runtime/common/asn1/Makefile.kup | 0 src/VBox/Runtime/common/asn1/asn1-basics.cpp | 611 + src/VBox/Runtime/common/asn1/asn1-cursor.cpp | 678 + .../Runtime/common/asn1/asn1-default-allocator.cpp | 231 + src/VBox/Runtime/common/asn1/asn1-dump.cpp | 644 + .../Runtime/common/asn1/asn1-efence-allocator.cpp | 215 + src/VBox/Runtime/common/asn1/asn1-encode.cpp | 535 + .../Runtime/common/asn1/asn1-safer-allocator.cpp | 232 + .../common/asn1/asn1-ut-bitstring-decode.cpp | 141 + .../common/asn1/asn1-ut-bitstring-template.h | 55 + src/VBox/Runtime/common/asn1/asn1-ut-bitstring.cpp | 548 + .../Runtime/common/asn1/asn1-ut-boolean-decode.cpp | 93 + .../Runtime/common/asn1/asn1-ut-boolean-template.h | 55 + src/VBox/Runtime/common/asn1/asn1-ut-boolean.cpp | 218 + .../Runtime/common/asn1/asn1-ut-core-decode.cpp | 69 + .../Runtime/common/asn1/asn1-ut-core-template.h | 55 + src/VBox/Runtime/common/asn1/asn1-ut-core.cpp | 331 + .../Runtime/common/asn1/asn1-ut-dyntype-decode.cpp | 250 + src/VBox/Runtime/common/asn1/asn1-ut-dyntype.cpp | 197 + .../Runtime/common/asn1/asn1-ut-integer-decode.cpp | 97 + .../Runtime/common/asn1/asn1-ut-integer-template.h | 55 + src/VBox/Runtime/common/asn1/asn1-ut-integer.cpp | 508 + .../Runtime/common/asn1/asn1-ut-null-decode.cpp | 73 + src/VBox/Runtime/common/asn1/asn1-ut-null.cpp | 141 + .../Runtime/common/asn1/asn1-ut-objid-decode.cpp | 377 + .../Runtime/common/asn1/asn1-ut-objid-template.h | 64 + src/VBox/Runtime/common/asn1/asn1-ut-objid.cpp | 566 + .../common/asn1/asn1-ut-octetstring-decode.cpp | 90 + .../common/asn1/asn1-ut-octetstring-template.h | 55 + .../Runtime/common/asn1/asn1-ut-octetstring.cpp | 459 + .../Runtime/common/asn1/asn1-ut-string-decode.cpp | 199 + .../Runtime/common/asn1/asn1-ut-string-template.h | 55 + .../Runtime/common/asn1/asn1-ut-string-template2.h | 49 + src/VBox/Runtime/common/asn1/asn1-ut-string.cpp | 1855 + .../Runtime/common/asn1/asn1-ut-time-decode.cpp | 415 + .../Runtime/common/asn1/asn1-ut-time-template.h | 55 + .../Runtime/common/asn1/asn1-ut-time-template2.h | 40 + src/VBox/Runtime/common/asn1/asn1-ut-time.cpp | 359 + src/VBox/Runtime/common/asn1/oiddb.cfg | 351 + src/VBox/Runtime/common/asn1/oiddb2c.cpp | 635 + src/VBox/Runtime/common/checksum/Makefile.kup | 0 src/VBox/Runtime/common/checksum/RTSha1Digest.cpp | 201 + .../Runtime/common/checksum/RTSha256Digest.cpp | 201 + src/VBox/Runtime/common/checksum/adler32.cpp | 181 + src/VBox/Runtime/common/checksum/alt-md2.cpp | 282 + src/VBox/Runtime/common/checksum/alt-md4.cpp | 293 + src/VBox/Runtime/common/checksum/alt-md5.cpp | 374 + src/VBox/Runtime/common/checksum/alt-sha1.cpp | 535 + src/VBox/Runtime/common/checksum/alt-sha256.cpp | 693 + src/VBox/Runtime/common/checksum/alt-sha3.cpp | 642 + src/VBox/Runtime/common/checksum/alt-sha512.cpp | 805 + src/VBox/Runtime/common/checksum/crc16ccitt.cpp | 118 + src/VBox/Runtime/common/checksum/crc32-zlib.cpp | 99 + src/VBox/Runtime/common/checksum/crc32.cpp | 196 + src/VBox/Runtime/common/checksum/crc32c.cpp | 132 + src/VBox/Runtime/common/checksum/crc64.cpp | 196 + src/VBox/Runtime/common/checksum/ipv4.cpp | 774 + src/VBox/Runtime/common/checksum/ipv6.cpp | 136 + src/VBox/Runtime/common/checksum/manifest-file.cpp | 94 + src/VBox/Runtime/common/checksum/manifest.cpp | 593 + src/VBox/Runtime/common/checksum/manifest2.cpp | 1477 + src/VBox/Runtime/common/checksum/manifest3.cpp | 667 + src/VBox/Runtime/common/checksum/md2str.cpp | 59 + src/VBox/Runtime/common/checksum/md4str.cpp | 59 + src/VBox/Runtime/common/checksum/md5str.cpp | 59 + src/VBox/Runtime/common/checksum/openssl-md2.cpp | 96 + src/VBox/Runtime/common/checksum/openssl-md4.cpp | 94 + src/VBox/Runtime/common/checksum/openssl-md5.cpp | 84 + src/VBox/Runtime/common/checksum/openssl-sha1.cpp | 100 + .../Runtime/common/checksum/openssl-sha256.cpp | 150 + src/VBox/Runtime/common/checksum/openssl-sha3.cpp | 270 + .../Runtime/common/checksum/openssl-sha512.cpp | 151 + src/VBox/Runtime/common/checksum/sha1str.cpp | 59 + src/VBox/Runtime/common/checksum/sha224str.cpp | 59 + src/VBox/Runtime/common/checksum/sha256str.cpp | 59 + src/VBox/Runtime/common/checksum/sha384str.cpp | 59 + src/VBox/Runtime/common/checksum/sha512str.cpp | 59 + src/VBox/Runtime/common/checksum/sha512t224str.cpp | 59 + src/VBox/Runtime/common/checksum/sha512t256str.cpp | 59 + .../Runtime/common/compiler/vcc/atexit-vcc.cpp | 145 + .../Runtime/common/compiler/vcc/except-seh-vcc.cpp | 243 + src/VBox/Runtime/common/compiler/vcc/except-vcc.h | 251 + .../common/compiler/vcc/except-x86-vcc-asm.asm | 358 + .../Runtime/common/compiler/vcc/except-x86-vcc.cpp | 329 + .../Runtime/common/compiler/vcc/fltused-vcc.cpp | 58 + src/VBox/Runtime/common/compiler/vcc/ftol2-vcc.asm | 80 + src/VBox/Runtime/common/compiler/vcc/guard-vcc.asm | 108 + .../common/compiler/vcc/initializers-c-cpp-vcc.cpp | 158 + src/VBox/Runtime/common/compiler/vcc/loadcfg-vcc.c | 154 + .../Runtime/common/compiler/vcc/purecall-vcc.cpp | 63 + .../common/compiler/vcc/stack-except-seh-vcc.cpp | 117 + .../common/compiler/vcc/stack-except-vcc.cpp | 106 + .../common/compiler/vcc/stack-probe-vcc.asm | 157 + src/VBox/Runtime/common/compiler/vcc/stack-vcc.asm | 639 + .../Runtime/common/compiler/vcc/stacksup-vcc.cpp | 371 + src/VBox/Runtime/common/compiler/vcc/tlsdir-vcc.c | 114 + src/VBox/Runtime/common/crypto/Makefile.kup | 0 .../common/crypto/RTCrPkcs5Pbkdf2Hmac-openssl.cpp | 86 + .../common/crypto/RTCrRandBytes-openssl.cpp | 69 + .../crypto/RTCrStoreCertAddFromJavaKeyStore.cpp | 320 + ...RTCrStoreCertAddWantedFromFishingExpedition.cpp | 256 + .../common/crypto/RTCrStoreCertExportAsPem.cpp | 147 + ...teSnapshotOfUserAndSystemTrustedCAsAndCerts.cpp | 89 + src/VBox/Runtime/common/crypto/cipher-openssl.cpp | 596 + src/VBox/Runtime/common/crypto/digest-builtin.cpp | 1175 + src/VBox/Runtime/common/crypto/digest-core.cpp | 499 + src/VBox/Runtime/common/crypto/digest-vfs.cpp | 80 + src/VBox/Runtime/common/crypto/iprt-openssl.cpp | 202 + .../common/crypto/key-create-rsa-openssl.cpp | 104 + src/VBox/Runtime/common/crypto/key-file.cpp | 688 + src/VBox/Runtime/common/crypto/key-internal.h | 132 + src/VBox/Runtime/common/crypto/key-openssl.cpp | 335 + src/VBox/Runtime/common/crypto/key.cpp | 557 + src/VBox/Runtime/common/crypto/pemfile-read.cpp | 663 + src/VBox/Runtime/common/crypto/pemfile-write.cpp | 266 + .../Runtime/common/crypto/pkcs7-asn1-decoder.cpp | 174 + src/VBox/Runtime/common/crypto/pkcs7-core.cpp | 250 + src/VBox/Runtime/common/crypto/pkcs7-file.cpp | 119 + src/VBox/Runtime/common/crypto/pkcs7-init.cpp | 62 + src/VBox/Runtime/common/crypto/pkcs7-internal.h | 47 + src/VBox/Runtime/common/crypto/pkcs7-sanity.cpp | 221 + src/VBox/Runtime/common/crypto/pkcs7-sign.cpp | 592 + src/VBox/Runtime/common/crypto/pkcs7-template.h | 236 + src/VBox/Runtime/common/crypto/pkcs7-verify.cpp | 866 + .../Runtime/common/crypto/pkcs8-asn1-decoder.cpp | 53 + src/VBox/Runtime/common/crypto/pkcs8-core.cpp | 54 + src/VBox/Runtime/common/crypto/pkcs8-init.cpp | 53 + src/VBox/Runtime/common/crypto/pkcs8-internal.h | 46 + src/VBox/Runtime/common/crypto/pkcs8-sanity.cpp | 53 + src/VBox/Runtime/common/crypto/pkcs8-template.h | 69 + src/VBox/Runtime/common/crypto/pkix-sign.cpp | 262 + .../common/crypto/pkix-signature-builtin.cpp | 153 + .../Runtime/common/crypto/pkix-signature-builtin.h | 51 + .../Runtime/common/crypto/pkix-signature-core.cpp | 294 + .../Runtime/common/crypto/pkix-signature-ossl.cpp | 288 + .../Runtime/common/crypto/pkix-signature-rsa.cpp | 565 + src/VBox/Runtime/common/crypto/pkix-util.cpp | 108 + src/VBox/Runtime/common/crypto/pkix-verify.cpp | 329 + src/VBox/Runtime/common/crypto/rc4-openssl.cpp | 75 + .../Runtime/common/crypto/rsa-asn1-decoder.cpp | 53 + src/VBox/Runtime/common/crypto/rsa-core.cpp | 54 + src/VBox/Runtime/common/crypto/rsa-init.cpp | 53 + src/VBox/Runtime/common/crypto/rsa-internal.h | 50 + src/VBox/Runtime/common/crypto/rsa-sanity.cpp | 53 + src/VBox/Runtime/common/crypto/rsa-template.h | 118 + .../Runtime/common/crypto/spc-asn1-decoder.cpp | 88 + src/VBox/Runtime/common/crypto/spc-core.cpp | 94 + src/VBox/Runtime/common/crypto/spc-init.cpp | 55 + src/VBox/Runtime/common/crypto/spc-internal.h | 47 + src/VBox/Runtime/common/crypto/spc-sanity.cpp | 179 + src/VBox/Runtime/common/crypto/spc-template.h | 198 + src/VBox/Runtime/common/crypto/ssl-openssl.cpp | 507 + .../Runtime/common/crypto/store-cert-add-basic.cpp | 865 + src/VBox/Runtime/common/crypto/store-inmem.cpp | 466 + src/VBox/Runtime/common/crypto/store-internal.h | 177 + src/VBox/Runtime/common/crypto/store.cpp | 571 + .../Runtime/common/crypto/taf-asn1-decoder.cpp | 54 + src/VBox/Runtime/common/crypto/taf-core.cpp | 51 + src/VBox/Runtime/common/crypto/taf-init.cpp | 54 + src/VBox/Runtime/common/crypto/taf-internal.h | 47 + src/VBox/Runtime/common/crypto/taf-sanity.cpp | 52 + src/VBox/Runtime/common/crypto/taf-template.h | 104 + .../Runtime/common/crypto/tsp-asn1-decoder.cpp | 51 + src/VBox/Runtime/common/crypto/tsp-core.cpp | 51 + src/VBox/Runtime/common/crypto/tsp-init.cpp | 51 + src/VBox/Runtime/common/crypto/tsp-internal.h | 47 + src/VBox/Runtime/common/crypto/tsp-sanity.cpp | 51 + src/VBox/Runtime/common/crypto/tsp-template.h | 113 + .../Runtime/common/crypto/x509-asn1-decoder.cpp | 230 + src/VBox/Runtime/common/crypto/x509-certpaths.cpp | 3017 ++ src/VBox/Runtime/common/crypto/x509-core.cpp | 1934 + src/VBox/Runtime/common/crypto/x509-file.cpp | 174 + src/VBox/Runtime/common/crypto/x509-init.cpp | 88 + src/VBox/Runtime/common/crypto/x509-internal.h | 47 + src/VBox/Runtime/common/crypto/x509-sanity.cpp | 171 + src/VBox/Runtime/common/crypto/x509-template.h | 468 + src/VBox/Runtime/common/crypto/x509-verify.cpp | 131 + src/VBox/Runtime/common/dbg/Makefile.kup | 0 src/VBox/Runtime/common/dbg/dbg.cpp | 122 + src/VBox/Runtime/common/dbg/dbgas.cpp | 1513 + src/VBox/Runtime/common/dbg/dbgcfg.cpp | 2525 + src/VBox/Runtime/common/dbg/dbgmod.cpp | 2317 + src/VBox/Runtime/common/dbg/dbgmodcodeview.cpp | 3197 ++ src/VBox/Runtime/common/dbg/dbgmodcontainer.cpp | 1050 + src/VBox/Runtime/common/dbg/dbgmoddbghelp.cpp | 537 + src/VBox/Runtime/common/dbg/dbgmoddeferred.cpp | 730 + src/VBox/Runtime/common/dbg/dbgmoddwarf.cpp | 6287 +++ src/VBox/Runtime/common/dbg/dbgmodexports.cpp | 183 + src/VBox/Runtime/common/dbg/dbgmodghidra.cpp | 526 + src/VBox/Runtime/common/dbg/dbgmodldr.cpp | 286 + src/VBox/Runtime/common/dbg/dbgmodmapsym.cpp | 622 + src/VBox/Runtime/common/dbg/dbgmodnm.cpp | 581 + .../common/dbg/dbgstackdumpself-amd64-x86.asm | 157 + src/VBox/Runtime/common/dbg/dbgstackdumpself.cpp | 543 + src/VBox/Runtime/common/dvm/Makefile.kup | 0 src/VBox/Runtime/common/dvm/dvm.cpp | 940 + src/VBox/Runtime/common/dvm/dvmbsdlabel.cpp | 707 + src/VBox/Runtime/common/dvm/dvmgpt.cpp | 755 + src/VBox/Runtime/common/dvm/dvmmbr.cpp | 1069 + src/VBox/Runtime/common/dvm/dvmvfs.cpp | 1467 + src/VBox/Runtime/common/efi/efiguid.cpp | 134 + src/VBox/Runtime/common/efi/efisignaturedb.cpp | 587 + src/VBox/Runtime/common/efi/efitime.cpp | 123 + src/VBox/Runtime/common/efi/efivarstorevfs.cpp | 2653 ++ src/VBox/Runtime/common/err/Makefile.kup | 0 .../Runtime/common/err/RTErrConvertFromErrno.cpp | 467 + .../Runtime/common/err/RTErrConvertToErrno.cpp | 465 + src/VBox/Runtime/common/err/errinfo-alloc.cpp | 80 + src/VBox/Runtime/common/err/errinfo.cpp | 137 + src/VBox/Runtime/common/err/errinfolog.cpp | 215 + src/VBox/Runtime/common/err/errmsg-sorter.cpp | 483 + src/VBox/Runtime/common/err/errmsg.cpp | 338 + src/VBox/Runtime/common/err/errmsg.sed | 98 + src/VBox/Runtime/common/err/errmsgcom.sed | 76 + src/VBox/Runtime/common/err/errmsgxpcom.cpp | 170 + src/VBox/Runtime/common/err/nocrt-strerror.cpp | 464 + src/VBox/Runtime/common/file/nocrt-close.cpp | 64 + src/VBox/Runtime/common/file/nocrt-dup.cpp | 78 + src/VBox/Runtime/common/file/nocrt-fstat.cpp | 81 + src/VBox/Runtime/common/file/nocrt-isatty.cpp | 72 + src/VBox/Runtime/common/file/nocrt-open.cpp | 104 + src/VBox/Runtime/common/file/nocrt-read.cpp | 65 + src/VBox/Runtime/common/fs/Makefile.kup | 0 src/VBox/Runtime/common/fs/RTFsCmdLs.cpp | 1862 + src/VBox/Runtime/common/fs/extvfs.cpp | 2860 ++ src/VBox/Runtime/common/fs/fatvfs.cpp | 6374 +++ src/VBox/Runtime/common/fs/isomaker.cpp | 7585 +++ src/VBox/Runtime/common/fs/isomakercmd-man.xml | 590 + src/VBox/Runtime/common/fs/isomakercmd.cpp | 3689 ++ src/VBox/Runtime/common/fs/isomakerimport.cpp | 2738 ++ src/VBox/Runtime/common/fs/isovfs.cpp | 7209 +++ src/VBox/Runtime/common/fs/ntfsvfs.cpp | 5698 +++ src/VBox/Runtime/common/fs/xfsvfs.cpp | 2460 + src/VBox/Runtime/common/fuzz/Makefile.kup | 0 src/VBox/Runtime/common/fuzz/fuzz-config.cpp | 651 + src/VBox/Runtime/common/fuzz/fuzz-observer.cpp | 1402 + .../Runtime/common/fuzz/fuzz-target-recorder.cpp | 797 + src/VBox/Runtime/common/fuzz/fuzz.cpp | 2322 + src/VBox/Runtime/common/fuzz/fuzzclientcmd.cpp | 329 + src/VBox/Runtime/common/fuzz/fuzzmastercmd.cpp | 1877 + .../common/ioqueue/ioqueue-aiofile-provider.cpp | 346 + .../common/ioqueue/ioqueue-stdfile-provider.cpp | 548 + src/VBox/Runtime/common/ioqueue/ioqueuebase.cpp | 297 + src/VBox/Runtime/common/ldr/Makefile.kup | 0 src/VBox/Runtime/common/ldr/ldr.cpp | 186 + src/VBox/Runtime/common/ldr/ldrELF.cpp | 381 + .../Runtime/common/ldr/ldrELFRelocatable.cpp.h | 3163 ++ src/VBox/Runtime/common/ldr/ldrEx.cpp | 773 + src/VBox/Runtime/common/ldr/ldrFile.cpp | 317 + src/VBox/Runtime/common/ldr/ldrLX.cpp | 3121 ++ src/VBox/Runtime/common/ldr/ldrMachO.cpp | 5734 +++ src/VBox/Runtime/common/ldr/ldrMemory.cpp | 336 + src/VBox/Runtime/common/ldr/ldrNative.cpp | 336 + src/VBox/Runtime/common/ldr/ldrPE.cpp | 5194 ++ src/VBox/Runtime/common/ldr/ldrVfsFile.cpp | 293 + src/VBox/Runtime/common/log/Makefile.kup | 0 src/VBox/Runtime/common/log/RTLogCreateEx.cpp | 68 + .../Runtime/common/log/RTLogSetR0ThreadNameF.cpp | 57 + src/VBox/Runtime/common/log/log-weak-assert.cpp | 55 + src/VBox/Runtime/common/log/log-weak-rel.cpp | 55 + src/VBox/Runtime/common/log/log-weak.cpp | 86 + src/VBox/Runtime/common/log/log.cpp | 4337 ++ src/VBox/Runtime/common/log/logcom.cpp | 156 + src/VBox/Runtime/common/log/logellipsis.cpp | 115 + src/VBox/Runtime/common/log/logformat.cpp | 110 + src/VBox/Runtime/common/log/logrel.cpp | 106 + src/VBox/Runtime/common/log/logrelellipsis.cpp | 88 + src/VBox/Runtime/common/log/tracebuf.cpp | 698 + src/VBox/Runtime/common/log/tracedefault.cpp | 92 + src/VBox/Runtime/common/log/tracelogreader.cpp | 1945 + src/VBox/Runtime/common/log/tracelogwriter.cpp | 969 + src/VBox/Runtime/common/math/Makefile.kup | 0 src/VBox/Runtime/common/math/RTUInt128MulByU64.asm | 91 + .../Runtime/common/math/RTUInt128MulByU64Ex.asm | 95 + src/VBox/Runtime/common/math/__fpclassifyd.cpp | 66 + src/VBox/Runtime/common/math/__fpclassifyf.cpp | 66 + src/VBox/Runtime/common/math/__fpclassifyl.cpp | 101 + src/VBox/Runtime/common/math/__isfinite.cpp | 57 + src/VBox/Runtime/common/math/__isfinitef.cpp | 57 + src/VBox/Runtime/common/math/__isfinitel.cpp | 63 + src/VBox/Runtime/common/math/__isinff.cpp | 57 + src/VBox/Runtime/common/math/__isinfl.cpp | 63 + src/VBox/Runtime/common/math/__isnanl.cpp | 63 + src/VBox/Runtime/common/math/__isnormal.cpp | 57 + src/VBox/Runtime/common/math/__isnormalf.cpp | 57 + src/VBox/Runtime/common/math/__isnormall.cpp | 63 + src/VBox/Runtime/common/math/__signbit.cpp | 57 + src/VBox/Runtime/common/math/__signbitf.cpp | 57 + src/VBox/Runtime/common/math/__signbitl.cpp | 62 + src/VBox/Runtime/common/math/atan.asm | 77 + src/VBox/Runtime/common/math/atan2.asm | 72 + src/VBox/Runtime/common/math/atan2f.asm | 72 + src/VBox/Runtime/common/math/atanf.asm | 77 + src/VBox/Runtime/common/math/bignum-amd64-x86.asm | 891 + src/VBox/Runtime/common/math/bignum.cpp | 2877 ++ src/VBox/Runtime/common/math/ceil.asm | 79 + src/VBox/Runtime/common/math/ceilf.asm | 79 + src/VBox/Runtime/common/math/ceill.asm | 70 + src/VBox/Runtime/common/math/consts.c | 59 + src/VBox/Runtime/common/math/copysign.cpp | 63 + src/VBox/Runtime/common/math/copysignf.cpp | 63 + src/VBox/Runtime/common/math/copysignl.cpp | 66 + src/VBox/Runtime/common/math/cos.asm | 213 + src/VBox/Runtime/common/math/cosf.asm | 213 + src/VBox/Runtime/common/math/cosl.asm | 72 + src/VBox/Runtime/common/math/exp.asm | 151 + src/VBox/Runtime/common/math/exp2.asm | 117 + src/VBox/Runtime/common/math/exp2f.asm | 117 + src/VBox/Runtime/common/math/expf.asm | 151 + src/VBox/Runtime/common/math/fabs.asm | 73 + src/VBox/Runtime/common/math/fabsf.asm | 72 + src/VBox/Runtime/common/math/fabsl.asm | 61 + src/VBox/Runtime/common/math/feclearexcept.asm | 121 + src/VBox/Runtime/common/math/fedisableexcept.asm | 117 + src/VBox/Runtime/common/math/feenableexcept.asm | 121 + src/VBox/Runtime/common/math/fegetenv.asm | 90 + src/VBox/Runtime/common/math/fegetexcept.asm | 82 + src/VBox/Runtime/common/math/fegetexceptflag.asm | 117 + src/VBox/Runtime/common/math/fegetround.asm | 79 + src/VBox/Runtime/common/math/fegetx87precision.asm | 70 + src/VBox/Runtime/common/math/feholdexcept.asm | 99 + src/VBox/Runtime/common/math/feraiseexcept.asm | 188 + src/VBox/Runtime/common/math/fesetenv.asm | 194 + src/VBox/Runtime/common/math/fesetexceptflag.asm | 127 + src/VBox/Runtime/common/math/fesetround.asm | 107 + src/VBox/Runtime/common/math/fesetx87precision.asm | 88 + src/VBox/Runtime/common/math/fetestexcept.asm | 107 + src/VBox/Runtime/common/math/feupdateenv.asm | 128 + src/VBox/Runtime/common/math/floor.asm | 78 + src/VBox/Runtime/common/math/floorf.asm | 78 + src/VBox/Runtime/common/math/floorl.asm | 69 + src/VBox/Runtime/common/math/fma-asm.asm | 104 + src/VBox/Runtime/common/math/fma.cpp | 100 + src/VBox/Runtime/common/math/fmaf-asm.asm | 104 + src/VBox/Runtime/common/math/fmaf.cpp | 101 + src/VBox/Runtime/common/math/fmax.cpp | 64 + src/VBox/Runtime/common/math/fmaxf.cpp | 64 + src/VBox/Runtime/common/math/fmaxl.cpp | 64 + src/VBox/Runtime/common/math/fmin.cpp | 64 + src/VBox/Runtime/common/math/fminf.cpp | 64 + src/VBox/Runtime/common/math/fminl.cpp | 64 + src/VBox/Runtime/common/math/frexp.cpp | 88 + src/VBox/Runtime/common/math/frexpf.cpp | 87 + src/VBox/Runtime/common/math/frexpl.cpp | 167 + src/VBox/Runtime/common/math/gcc/Makefile.kup | 0 src/VBox/Runtime/common/math/gcc/adddi3.c | 63 + src/VBox/Runtime/common/math/gcc/anddi3.c | 61 + src/VBox/Runtime/common/math/gcc/ashldi3.c | 70 + src/VBox/Runtime/common/math/gcc/ashrdi3.c | 82 + src/VBox/Runtime/common/math/gcc/cmpdi2.c | 62 + src/VBox/Runtime/common/math/gcc/divdi3.c | 70 + src/VBox/Runtime/common/math/gcc/divmoddi4.c | 84 + src/VBox/Runtime/common/math/gcc/iordi3.c | 61 + src/VBox/Runtime/common/math/gcc/lshldi3.c | 70 + src/VBox/Runtime/common/math/gcc/lshrdi3.c | 69 + src/VBox/Runtime/common/math/gcc/moddi3.c | 70 + src/VBox/Runtime/common/math/gcc/muldi3.c | 249 + src/VBox/Runtime/common/math/gcc/negdi2.c | 60 + src/VBox/Runtime/common/math/gcc/notdi2.c | 61 + src/VBox/Runtime/common/math/gcc/qdivrem.c | 285 + src/VBox/Runtime/common/math/gcc/quad.h | 174 + src/VBox/Runtime/common/math/gcc/subdi3.c | 62 + src/VBox/Runtime/common/math/gcc/ucmpdi2.c | 61 + src/VBox/Runtime/common/math/gcc/udivdi3.c | 56 + src/VBox/Runtime/common/math/gcc/udivmoddi4.c | 65 + src/VBox/Runtime/common/math/gcc/umoddi3.c | 58 + src/VBox/Runtime/common/math/gcc/xordi3.c | 61 + src/VBox/Runtime/common/math/isinf.cpp | 57 + src/VBox/Runtime/common/math/isnan.cpp | 57 + src/VBox/Runtime/common/math/isnanf.cpp | 57 + src/VBox/Runtime/common/math/ldexp.asm | 89 + src/VBox/Runtime/common/math/ldexpf.asm | 89 + src/VBox/Runtime/common/math/ldexpl.asm | 80 + src/VBox/Runtime/common/math/llrint.asm | 72 + src/VBox/Runtime/common/math/llrintf.asm | 72 + src/VBox/Runtime/common/math/llrintl.asm | 70 + src/VBox/Runtime/common/math/llround.cpp | 65 + src/VBox/Runtime/common/math/llroundf.cpp | 65 + src/VBox/Runtime/common/math/llroundl.cpp | 65 + src/VBox/Runtime/common/math/log.asm | 97 + src/VBox/Runtime/common/math/log2.asm | 229 + src/VBox/Runtime/common/math/log2f.asm | 227 + src/VBox/Runtime/common/math/logf.asm | 97 + src/VBox/Runtime/common/math/logl.asm | 84 + src/VBox/Runtime/common/math/lrint.asm | 75 + src/VBox/Runtime/common/math/lrintf.asm | 74 + src/VBox/Runtime/common/math/lrintl.asm | 77 + src/VBox/Runtime/common/math/lround.cpp | 65 + src/VBox/Runtime/common/math/lroundf.cpp | 65 + src/VBox/Runtime/common/math/lroundl.cpp | 65 + src/VBox/Runtime/common/math/nocrt-abs.cpp | 52 + src/VBox/Runtime/common/math/nocrt-labs.cpp | 52 + src/VBox/Runtime/common/math/nocrt-llabs.cpp | 52 + src/VBox/Runtime/common/math/pow.asm | 127 + src/VBox/Runtime/common/math/powcore.asm | 633 + src/VBox/Runtime/common/math/powf.asm | 127 + src/VBox/Runtime/common/math/remainder.asm | 104 + src/VBox/Runtime/common/math/remainderf.asm | 104 + src/VBox/Runtime/common/math/remainderl.asm | 88 + src/VBox/Runtime/common/math/rint.asm | 99 + src/VBox/Runtime/common/math/rintf.asm | 99 + src/VBox/Runtime/common/math/round.cpp | 69 + src/VBox/Runtime/common/math/roundf.cpp | 69 + src/VBox/Runtime/common/math/roundl.cpp | 69 + src/VBox/Runtime/common/math/rtNoCrtHasSse.asm | 78 + src/VBox/Runtime/common/math/sin.asm | 185 + src/VBox/Runtime/common/math/sincore.asm | 352 + src/VBox/Runtime/common/math/sinf.asm | 185 + src/VBox/Runtime/common/math/sinl.asm | 71 + src/VBox/Runtime/common/math/sqrt.asm | 65 + src/VBox/Runtime/common/math/sqrtf.asm | 65 + src/VBox/Runtime/common/math/tan.asm | 119 + src/VBox/Runtime/common/math/tanf.asm | 119 + src/VBox/Runtime/common/math/tanl.asm | 72 + src/VBox/Runtime/common/math/trunc.asm | 108 + src/VBox/Runtime/common/math/truncf.asm | 108 + src/VBox/Runtime/common/math/truncl.asm | 76 + src/VBox/Runtime/common/math/watcom/I8D-x86-32.asm | 108 + .../Runtime/common/math/watcom/RTWatcomUInt64Div.c | 48 + src/VBox/Runtime/common/math/watcom/U8D-x86-32.asm | 84 + .../Runtime/common/math/watcom/U8LS-x86-32.asm | 74 + .../Runtime/common/math/watcom/U8M-I8M-x86-32.asm | 87 + .../Runtime/common/math/watcom/U8RS-x86-32.asm | 73 + src/VBox/Runtime/common/misc/Makefile.kup | 0 src/VBox/Runtime/common/misc/RTAssertMsg1Weak.cpp | 52 + src/VBox/Runtime/common/misc/RTAssertMsg2.cpp | 55 + src/VBox/Runtime/common/misc/RTAssertMsg2Add.cpp | 55 + .../Runtime/common/misc/RTAssertMsg2AddWeak.cpp | 55 + .../Runtime/common/misc/RTAssertMsg2AddWeakV.cpp | 50 + src/VBox/Runtime/common/misc/RTAssertMsg2Weak.cpp | 55 + src/VBox/Runtime/common/misc/RTAssertMsg2WeakV.cpp | 50 + src/VBox/Runtime/common/misc/RTFileModeToFlags.cpp | 364 + src/VBox/Runtime/common/misc/RTFileOpenF.cpp | 54 + src/VBox/Runtime/common/misc/RTFileOpenV.cpp | 58 + .../Runtime/common/misc/RTMemWipeThoroughly.cpp | 65 + .../common/misc/RTSystemFirmwareTypeName.cpp | 60 + .../common/misc/RTSystemIsInsideVM-amd64-x86.cpp | 58 + src/VBox/Runtime/common/misc/assert.cpp | 348 + src/VBox/Runtime/common/misc/buildconfig.cpp | 152 + src/VBox/Runtime/common/misc/cidr.cpp | 129 + src/VBox/Runtime/common/misc/circbuf.cpp | 262 + src/VBox/Runtime/common/misc/expreval.cpp | 2740 ++ src/VBox/Runtime/common/misc/getopt.cpp | 922 + src/VBox/Runtime/common/misc/getoptargv.cpp | 654 + src/VBox/Runtime/common/misc/handle.cpp | 85 + src/VBox/Runtime/common/misc/handletable.cpp | 234 + src/VBox/Runtime/common/misc/handletable.h | 257 + src/VBox/Runtime/common/misc/handletablectx.cpp | 339 + src/VBox/Runtime/common/misc/handletablesimple.cpp | 314 + src/VBox/Runtime/common/misc/inifile.cpp | 733 + src/VBox/Runtime/common/misc/json.cpp | 1914 + src/VBox/Runtime/common/misc/lockvalidator.cpp | 4482 ++ src/VBox/Runtime/common/misc/message.cpp | 266 + src/VBox/Runtime/common/misc/messagerefentry.cpp | 332 + src/VBox/Runtime/common/misc/once.cpp | 450 + src/VBox/Runtime/common/misc/req.cpp | 537 + src/VBox/Runtime/common/misc/reqpool.cpp | 1299 + src/VBox/Runtime/common/misc/reqqueue.cpp | 465 + src/VBox/Runtime/common/misc/sanity-c.c | 37 + src/VBox/Runtime/common/misc/sanity-cpp.cpp | 38 + src/VBox/Runtime/common/misc/sanity.h | 225 + src/VBox/Runtime/common/misc/semspingpong.cpp | 218 + src/VBox/Runtime/common/misc/setjmp.asm | 148 + src/VBox/Runtime/common/misc/sg.cpp | 507 + src/VBox/Runtime/common/misc/term.cpp | 252 + src/VBox/Runtime/common/misc/thread.cpp | 1446 + src/VBox/Runtime/common/misc/uri.cpp | 1181 + src/VBox/Runtime/common/misc/zero-alt.S | 118 + src/VBox/Runtime/common/misc/zero.asm | 83 + src/VBox/Runtime/common/misc/zero.cpp | 52 + src/VBox/Runtime/common/net/Makefile.kup | 0 src/VBox/Runtime/common/net/macstr.cpp | 123 + src/VBox/Runtime/common/net/netaddrstr.cpp | 1233 + src/VBox/Runtime/common/net/netaddrstr2.cpp | 737 + src/VBox/Runtime/common/path/Makefile.kup | 0 src/VBox/Runtime/common/path/RTPathAbsDup.cpp | 49 + src/VBox/Runtime/common/path/RTPathAbsEx.cpp | 699 + src/VBox/Runtime/common/path/RTPathAbsExDup.cpp | 82 + src/VBox/Runtime/common/path/RTPathAppend.cpp | 51 + src/VBox/Runtime/common/path/RTPathAppendEx.cpp | 97 + src/VBox/Runtime/common/path/RTPathAppendEx.cpp.h | 166 + .../Runtime/common/path/RTPathCalcRelative.cpp | 248 + .../common/path/RTPathChangeToDosSlashes.cpp | 74 + .../common/path/RTPathChangeToUnixSlashes.cpp | 74 + .../Runtime/common/path/RTPathCopyComponents.cpp | 94 + .../Runtime/common/path/RTPathCountComponents.cpp | 62 + .../common/path/RTPathEnsureTrailingSeparator.cpp | 108 + src/VBox/Runtime/common/path/RTPathExt.cpp | 79 + src/VBox/Runtime/common/path/RTPathFilename.cpp | 109 + .../Runtime/common/path/RTPathFilenameUtf16.cpp | 109 + src/VBox/Runtime/common/path/RTPathFindCommon.cpp | 127 + .../Runtime/common/path/RTPathFindCommon.cpp.h | 267 + src/VBox/Runtime/common/path/RTPathGlob.cpp | 2177 + src/VBox/Runtime/common/path/RTPathHasExt.cpp | 50 + src/VBox/Runtime/common/path/RTPathHasPath.cpp | 61 + src/VBox/Runtime/common/path/RTPathJoin.cpp | 67 + src/VBox/Runtime/common/path/RTPathJoinA.cpp | 83 + src/VBox/Runtime/common/path/RTPathJoinEx.cpp | 70 + .../Runtime/common/path/RTPathParentLength.cpp | 95 + .../Runtime/common/path/RTPathParentLength.cpp.h | 68 + src/VBox/Runtime/common/path/RTPathParse.cpp | 85 + src/VBox/Runtime/common/path/RTPathParse.cpp.h | 256 + src/VBox/Runtime/common/path/RTPathParseSimple.cpp | 144 + .../Runtime/common/path/RTPathParsedReassemble.cpp | 161 + .../Runtime/common/path/RTPathPurgeFilename.cpp | 131 + src/VBox/Runtime/common/path/RTPathRealDup.cpp | 57 + src/VBox/Runtime/common/path/RTPathRmCmd.cpp | 658 + .../Runtime/common/path/RTPathSkipRootSpec.cpp | 51 + src/VBox/Runtime/common/path/RTPathSplit.cpp | 143 + src/VBox/Runtime/common/path/RTPathSplitA.cpp | 101 + .../Runtime/common/path/RTPathSplitReassemble.cpp | 130 + .../Runtime/common/path/RTPathStartsWithRoot.cpp | 51 + src/VBox/Runtime/common/path/RTPathStripExt.cpp | 53 + .../Runtime/common/path/RTPathStripFilename.cpp | 99 + .../common/path/RTPathStripTrailingSlash.cpp | 74 + .../Runtime/common/path/RTPathTraverseList.cpp | 90 + src/VBox/Runtime/common/path/comparepaths.cpp | 146 + src/VBox/Runtime/common/path/nocrt-access.cpp | 78 + src/VBox/Runtime/common/path/nocrt-unlink.cpp | 59 + src/VBox/Runtime/common/path/rtPathRootSpecLen.cpp | 103 + .../Runtime/common/path/rtPathVolumeSpecLen.cpp | 80 + .../common/path/rtpath-expand-template.cpp.h | 92 + .../common/path/rtpath-root-length-template.cpp.h | 80 + src/VBox/Runtime/common/rand/Makefile.kup | 0 src/VBox/Runtime/common/rand/nocrt-rand.cpp | 56 + src/VBox/Runtime/common/rand/rand.cpp | 185 + src/VBox/Runtime/common/rand/randadv.cpp | 424 + src/VBox/Runtime/common/rand/randparkmiller.cpp | 220 + src/VBox/Runtime/common/rest/Makefile.kup | 0 src/VBox/Runtime/common/rest/RTCRestAnyObject.cpp | 606 + src/VBox/Runtime/common/rest/RTCRestArrayBase.cpp | 496 + .../Runtime/common/rest/RTCRestClientApiBase.cpp | 326 + .../common/rest/RTCRestClientApiBaseOci.cpp | 185 + .../common/rest/RTCRestClientRequestBase.cpp | 280 + .../common/rest/RTCRestClientResponseBase.cpp | 413 + .../common/rest/RTCRestJsonPrimaryCursor.cpp | 120 + src/VBox/Runtime/common/rest/RTCRestOutputBase.cpp | 129 + .../common/rest/RTCRestOutputPrettyBase.cpp | 134 + .../common/rest/RTCRestOutputPrettyToString.cpp | 130 + .../Runtime/common/rest/RTCRestOutputToString.cpp | 130 + .../Runtime/common/rest/RTCRestStringMapBase.cpp | 467 + src/VBox/Runtime/common/rest/rest-binary.cpp | 708 + .../common/rest/rest-primary-object-types.cpp | 2403 + src/VBox/Runtime/common/sort/Makefile.kup | 0 src/VBox/Runtime/common/sort/RTSortApvIsSorted.cpp | 58 + src/VBox/Runtime/common/sort/RTSortIsSorted.cpp | 60 + src/VBox/Runtime/common/sort/nocrt-bsearch.cpp | 80 + src/VBox/Runtime/common/sort/nocrt-qsort.cpp | 70 + src/VBox/Runtime/common/sort/nocrt-qsort_r.cpp | 56 + src/VBox/Runtime/common/sort/shellsort.cpp | 111 + src/VBox/Runtime/common/string/Makefile.kup | 0 src/VBox/Runtime/common/string/RTStrCat.cpp | 68 + src/VBox/Runtime/common/string/RTStrCatEx.cpp | 70 + src/VBox/Runtime/common/string/RTStrCatP.cpp | 63 + src/VBox/Runtime/common/string/RTStrCatPEx.cpp | 62 + src/VBox/Runtime/common/string/RTStrCmp.cpp | 70 + src/VBox/Runtime/common/string/RTStrCopy.cpp | 64 + src/VBox/Runtime/common/string/RTStrCopyEx.cpp | 66 + src/VBox/Runtime/common/string/RTStrCopyP.cpp | 70 + src/VBox/Runtime/common/string/RTStrCopyPEx.cpp | 72 + src/VBox/Runtime/common/string/RTStrEnd.asm | 100 + src/VBox/Runtime/common/string/RTStrEnd.cpp | 57 + src/VBox/Runtime/common/string/RTStrFormat.cpp | 59 + src/VBox/Runtime/common/string/RTStrICmpAscii.cpp | 88 + .../Runtime/common/string/RTStrIStartsWith.cpp | 56 + src/VBox/Runtime/common/string/RTStrMemFind32.asm | 99 + src/VBox/Runtime/common/string/RTStrMemFind32.cpp | 60 + src/VBox/Runtime/common/string/RTStrNCmp.cpp | 72 + src/VBox/Runtime/common/string/RTStrNICmpAscii.cpp | 90 + src/VBox/Runtime/common/string/RTStrNLen.cpp | 51 + src/VBox/Runtime/common/string/RTStrNLenEx.cpp | 59 + .../Runtime/common/string/RTStrPrintHexBytes.cpp | 95 + src/VBox/Runtime/common/string/RTStrSplit.cpp | 133 + src/VBox/Runtime/common/string/RTStrStartsWith.cpp | 59 + src/VBox/Runtime/common/string/RTStrStr.cpp | 57 + src/VBox/Runtime/common/string/RTUtf16Cat.cpp | 54 + src/VBox/Runtime/common/string/RTUtf16CatAscii.cpp | 54 + src/VBox/Runtime/common/string/RTUtf16Chr.cpp | 62 + src/VBox/Runtime/common/string/RTUtf16CmpAscii.cpp | 60 + src/VBox/Runtime/common/string/RTUtf16Copy.cpp | 64 + .../Runtime/common/string/RTUtf16CopyAscii.cpp | 82 + src/VBox/Runtime/common/string/RTUtf16CopyEx.cpp | 65 + src/VBox/Runtime/common/string/RTUtf16End.cpp | 56 + .../Runtime/common/string/RTUtf16FindAscii.cpp | 78 + .../Runtime/common/string/RTUtf16ICmpAscii.cpp | 66 + src/VBox/Runtime/common/string/RTUtf16NCmp.cpp | 66 + .../Runtime/common/string/RTUtf16NCmpAscii.cpp | 61 + src/VBox/Runtime/common/string/RTUtf16NCmpUtf8.cpp | 74 + .../Runtime/common/string/RTUtf16NICmpAscii.cpp | 71 + src/VBox/Runtime/common/string/RTUtf16NLen.cpp | 53 + src/VBox/Runtime/common/string/RTUtf16NLenEx.cpp | 59 + .../Runtime/common/string/RTUtf16PrintHexBytes.cpp | 71 + src/VBox/Runtime/common/string/atoi.cpp | 64 + src/VBox/Runtime/common/string/base64-utf16.cpp | 446 + src/VBox/Runtime/common/string/base64.cpp | 539 + src/VBox/Runtime/common/string/base64.h | 101 + src/VBox/Runtime/common/string/bzero.asm | 137 + src/VBox/Runtime/common/string/memchr.asm | 103 + src/VBox/Runtime/common/string/memchr.cpp | 75 + src/VBox/Runtime/common/string/memcmp.asm | 155 + src/VBox/Runtime/common/string/memcmp.cpp | 97 + src/VBox/Runtime/common/string/memcpy.asm | 122 + src/VBox/Runtime/common/string/memcpy.cpp | 92 + src/VBox/Runtime/common/string/memmove.asm | 158 + src/VBox/Runtime/common/string/mempcpy.asm | 110 + src/VBox/Runtime/common/string/mempcpy.cpp | 55 + src/VBox/Runtime/common/string/memrchr.asm | 107 + src/VBox/Runtime/common/string/memrchr.cpp | 72 + src/VBox/Runtime/common/string/memset.asm | 141 + src/VBox/Runtime/common/string/memset.cpp | 86 + src/VBox/Runtime/common/string/ministring.cpp | 1210 + src/VBox/Runtime/common/string/nocrt-atof.cpp | 52 + src/VBox/Runtime/common/string/nocrt-scprintf.cpp | 58 + src/VBox/Runtime/common/string/nocrt-snprintf.cpp | 66 + src/VBox/Runtime/common/string/nocrt-sscanf.cpp | 57 + src/VBox/Runtime/common/string/nocrt-strdup.cpp | 52 + src/VBox/Runtime/common/string/nocrt-stricmp.cpp | 52 + src/VBox/Runtime/common/string/nocrt-strtod.cpp | 59 + src/VBox/Runtime/common/string/nocrt-strtok.cpp | 56 + src/VBox/Runtime/common/string/nocrt-strtol.cpp | 72 + src/VBox/Runtime/common/string/nocrt-strtoll.cpp | 69 + src/VBox/Runtime/common/string/nocrt-strtoul.cpp | 73 + src/VBox/Runtime/common/string/nocrt-strtoull.cpp | 70 + src/VBox/Runtime/common/string/nocrt-vscprintf.cpp | 65 + src/VBox/Runtime/common/string/nocrt-vsnprintf.cpp | 63 + src/VBox/Runtime/common/string/nocrt-vsscanf.cpp | 403 + src/VBox/Runtime/common/string/simplepattern.cpp | 204 + src/VBox/Runtime/common/string/straprintf.cpp | 207 + src/VBox/Runtime/common/string/strcache.cpp | 1239 + src/VBox/Runtime/common/string/strcat.cpp | 63 + src/VBox/Runtime/common/string/strchr.asm | 152 + src/VBox/Runtime/common/string/strchr.cpp | 67 + src/VBox/Runtime/common/string/strcmp.asm | 111 + src/VBox/Runtime/common/string/strcpy.asm | 103 + src/VBox/Runtime/common/string/strcpy.cpp | 66 + src/VBox/Runtime/common/string/strcspn.cpp | 65 + src/VBox/Runtime/common/string/strformat.cpp | 866 + src/VBox/Runtime/common/string/strformatfloat.cpp | 353 + src/VBox/Runtime/common/string/strformatnum.cpp | 245 + src/VBox/Runtime/common/string/strformatrt.cpp | 1722 + src/VBox/Runtime/common/string/strformattype.cpp | 487 + src/VBox/Runtime/common/string/strhash1.cpp | 82 + src/VBox/Runtime/common/string/stringalloc.cpp | 321 + src/VBox/Runtime/common/string/strlen.asm | 75 + src/VBox/Runtime/common/string/strlen.cpp | 66 + src/VBox/Runtime/common/string/strncat.cpp | 69 + src/VBox/Runtime/common/string/strncmp.asm | 141 + src/VBox/Runtime/common/string/strncmp.cpp | 70 + src/VBox/Runtime/common/string/strncpy.asm | 139 + src/VBox/Runtime/common/string/strnlen.cpp | 62 + src/VBox/Runtime/common/string/strpbrk.cpp | 81 + .../Runtime/common/string/strprintf-ellipsis.cpp | 67 + src/VBox/Runtime/common/string/strprintf.cpp | 131 + .../Runtime/common/string/strprintf2-ellipsis.cpp | 67 + src/VBox/Runtime/common/string/strprintf2.cpp | 150 + src/VBox/Runtime/common/string/strrchr.cpp | 52 + src/VBox/Runtime/common/string/strspace.cpp | 195 + src/VBox/Runtime/common/string/strstr.cpp | 78 + src/VBox/Runtime/common/string/strstrip.cpp | 103 + src/VBox/Runtime/common/string/strtofloat.cpp | 1400 + src/VBox/Runtime/common/string/strtok_r.cpp | 90 + src/VBox/Runtime/common/string/strtonum.cpp | 934 + src/VBox/Runtime/common/string/strversion.cpp | 235 + src/VBox/Runtime/common/string/uni.cpp | 53 + src/VBox/Runtime/common/string/unidata-flags.cpp | 47017 +++++++++++++++++++ src/VBox/Runtime/common/string/unidata-lower.cpp | 3911 ++ src/VBox/Runtime/common/string/unidata-upper.cpp | 4078 ++ src/VBox/Runtime/common/string/uniread.cpp | 1336 + src/VBox/Runtime/common/string/utf-16-case.cpp | 449 + src/VBox/Runtime/common/string/utf-16-latin-1.cpp | 491 + src/VBox/Runtime/common/string/utf-16-printf.cpp | 243 + src/VBox/Runtime/common/string/utf-16.cpp | 1329 + src/VBox/Runtime/common/string/utf-8-case.cpp | 318 + src/VBox/Runtime/common/string/utf-8-case2.cpp | 128 + src/VBox/Runtime/common/string/utf-8.cpp | 2043 + src/VBox/Runtime/common/string/watcom/bzero.asm | 42 + src/VBox/Runtime/common/string/watcom/memchr.asm | 42 + src/VBox/Runtime/common/string/watcom/memcmp.asm | 42 + src/VBox/Runtime/common/string/watcom/memcpy.asm | 42 + src/VBox/Runtime/common/string/watcom/memmove.asm | 42 + src/VBox/Runtime/common/string/watcom/mempcpy.asm | 42 + src/VBox/Runtime/common/string/watcom/memrchr.asm | 42 + src/VBox/Runtime/common/string/watcom/memset.asm | 42 + src/VBox/Runtime/common/string/watcom/strchr.asm | 42 + src/VBox/Runtime/common/string/watcom/strcmp.asm | 42 + src/VBox/Runtime/common/string/watcom/strcpy.asm | 42 + src/VBox/Runtime/common/string/watcom/strlen.asm | 42 + src/VBox/Runtime/common/string/watcom/strncmp.asm | 42 + src/VBox/Runtime/common/string/watcom/strncpy.asm | 42 + src/VBox/Runtime/common/string/wcslen.asm | 75 + src/VBox/Runtime/common/table/Makefile.kup | 0 src/VBox/Runtime/common/table/avl_Base.cpp.h | 474 + src/VBox/Runtime/common/table/avl_Destroy.cpp.h | 120 + src/VBox/Runtime/common/table/avl_DoWithAll.cpp.h | 156 + src/VBox/Runtime/common/table/avl_Enum.cpp.h | 105 + src/VBox/Runtime/common/table/avl_Get.cpp.h | 77 + src/VBox/Runtime/common/table/avl_GetBestFit.cpp.h | 113 + src/VBox/Runtime/common/table/avl_Range.cpp.h | 94 + .../Runtime/common/table/avl_RemoveBestFit.cpp.h | 80 + src/VBox/Runtime/common/table/avl_RemoveNode.cpp.h | 153 + src/VBox/Runtime/common/table/avlgcphys.cpp | 88 + src/VBox/Runtime/common/table/avlgcptr.cpp | 88 + src/VBox/Runtime/common/table/avlhcphys.cpp | 88 + src/VBox/Runtime/common/table/avllu32.cpp | 89 + src/VBox/Runtime/common/table/avlogcphys.cpp | 89 + src/VBox/Runtime/common/table/avlogcptr.cpp | 90 + src/VBox/Runtime/common/table/avlohcphys.cpp | 89 + src/VBox/Runtime/common/table/avloioport.cpp | 89 + src/VBox/Runtime/common/table/avlou32.cpp | 90 + src/VBox/Runtime/common/table/avlpv.cpp | 88 + src/VBox/Runtime/common/table/avlrfoff.cpp | 94 + src/VBox/Runtime/common/table/avlrgcptr.cpp | 94 + src/VBox/Runtime/common/table/avlrogcphys.cpp | 95 + src/VBox/Runtime/common/table/avlrogcptr.cpp | 95 + src/VBox/Runtime/common/table/avlroioport.cpp | 93 + src/VBox/Runtime/common/table/avlroogcptr.cpp | 102 + src/VBox/Runtime/common/table/avlrpv.cpp | 93 + src/VBox/Runtime/common/table/avlru64.cpp | 93 + src/VBox/Runtime/common/table/avlruintptr.cpp | 94 + src/VBox/Runtime/common/table/avlu32.cpp | 88 + src/VBox/Runtime/common/table/avlu64.cpp | 88 + src/VBox/Runtime/common/table/avluintptr.cpp | 88 + src/VBox/Runtime/common/table/avlul.cpp | 88 + src/VBox/Runtime/common/table/table.cpp | 42 + src/VBox/Runtime/common/time/Makefile.kup | 0 .../Runtime/common/time/RTTimeFormatDurationEx.cpp | 267 + src/VBox/Runtime/common/time/time.cpp | 1492 + src/VBox/Runtime/common/time/timeprog.cpp | 107 + src/VBox/Runtime/common/time/timesup.cpp | 467 + src/VBox/Runtime/common/time/timesupA.asm | 161 + src/VBox/Runtime/common/time/timesupA.mac | 895 + src/VBox/Runtime/common/time/timesupref.cpp | 318 + src/VBox/Runtime/common/time/timesupref.h | 408 + src/VBox/Runtime/common/time/timesysalias.cpp | 67 + src/VBox/Runtime/common/time/timezoneinfo-gen.py | 470 + src/VBox/Runtime/common/time/timezoneinfo.cpp | 1171 + src/VBox/Runtime/common/vfs/Makefile.kup | 0 src/VBox/Runtime/common/vfs/vfsbase.cpp | 4385 ++ src/VBox/Runtime/common/vfs/vfschain.cpp | 1861 + src/VBox/Runtime/common/vfs/vfsfss2dir.cpp | 445 + src/VBox/Runtime/common/vfs/vfsiosmisc.cpp | 238 + src/VBox/Runtime/common/vfs/vfsmemory.cpp | 971 + src/VBox/Runtime/common/vfs/vfsmisc.cpp | 99 + src/VBox/Runtime/common/vfs/vfsmount.cpp | 584 + src/VBox/Runtime/common/vfs/vfsmsg.cpp | 78 + src/VBox/Runtime/common/vfs/vfsprintf.cpp | 165 + src/VBox/Runtime/common/vfs/vfsprogress.cpp | 554 + src/VBox/Runtime/common/vfs/vfsreadahead.cpp | 1013 + src/VBox/Runtime/common/vfs/vfsstddir.cpp | 867 + src/VBox/Runtime/common/vfs/vfsstdfile.cpp | 669 + src/VBox/Runtime/common/vfs/vfsstdpipe.cpp | 324 + src/VBox/Runtime/common/zip/Makefile.kup | 0 src/VBox/Runtime/common/zip/cpiovfs.cpp | 1146 + src/VBox/Runtime/common/zip/cpiovfsreader.h | 169 + src/VBox/Runtime/common/zip/gzipcmd.cpp | 606 + src/VBox/Runtime/common/zip/gzipvfs.cpp | 1035 + src/VBox/Runtime/common/zip/pkzip.cpp | 259 + src/VBox/Runtime/common/zip/pkzipvfs.cpp | 1296 + src/VBox/Runtime/common/zip/tar.h | 146 + src/VBox/Runtime/common/zip/tarcmd.cpp | 1960 + src/VBox/Runtime/common/zip/tarvfs.cpp | 1477 + src/VBox/Runtime/common/zip/tarvfsreader.h | 179 + src/VBox/Runtime/common/zip/tarvfswriter.cpp | 2363 + src/VBox/Runtime/common/zip/unzipcmd.cpp | 480 + src/VBox/Runtime/common/zip/xarvfs.cpp | 2156 + src/VBox/Runtime/common/zip/zip.cpp | 2016 + 844 files changed, 344645 insertions(+) create mode 100644 src/VBox/Runtime/common/Makefile.kup create mode 100644 src/VBox/Runtime/common/alloc/Makefile.kup create mode 100644 src/VBox/Runtime/common/alloc/alloc.cpp create mode 100644 src/VBox/Runtime/common/alloc/heapoffset.cpp create mode 100644 src/VBox/Runtime/common/alloc/heapsimple.cpp create mode 100644 src/VBox/Runtime/common/alloc/memcache.cpp create mode 100644 src/VBox/Runtime/common/alloc/memtracker.cpp create mode 100644 src/VBox/Runtime/common/asm/ASMAddFlags.asm create mode 100644 src/VBox/Runtime/common/asm/ASMAtomicCmpXchgExU64.asm create mode 100644 src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU16.asm create mode 100644 src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU64.asm create mode 100644 src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU8.asm create mode 100644 src/VBox/Runtime/common/asm/ASMAtomicReadU64.asm create mode 100644 src/VBox/Runtime/common/asm/ASMAtomicUoAndU32.asm create mode 100644 src/VBox/Runtime/common/asm/ASMAtomicUoAndU64.asm create mode 100644 src/VBox/Runtime/common/asm/ASMAtomicUoDecU32.asm create mode 100644 src/VBox/Runtime/common/asm/ASMAtomicUoIncU32.asm create mode 100644 src/VBox/Runtime/common/asm/ASMAtomicUoOrU32.asm create mode 100644 src/VBox/Runtime/common/asm/ASMAtomicUoOrU64.asm create mode 100644 src/VBox/Runtime/common/asm/ASMAtomicUoReadU64.asm create mode 100644 src/VBox/Runtime/common/asm/ASMAtomicUoXorU32.asm create mode 100644 src/VBox/Runtime/common/asm/ASMAtomicXchgU16.asm create mode 100644 src/VBox/Runtime/common/asm/ASMAtomicXchgU64.asm create mode 100644 src/VBox/Runtime/common/asm/ASMBitFirstClear-generic.cpp create mode 100644 src/VBox/Runtime/common/asm/ASMBitFirstClear.asm create mode 100644 src/VBox/Runtime/common/asm/ASMBitFirstSet-generic.cpp create mode 100644 src/VBox/Runtime/common/asm/ASMBitFirstSet.asm create mode 100644 src/VBox/Runtime/common/asm/ASMBitFirstSetU16.asm create mode 100644 src/VBox/Runtime/common/asm/ASMBitFirstSetU32.asm create mode 100644 src/VBox/Runtime/common/asm/ASMBitFirstSetU64.asm create mode 100644 src/VBox/Runtime/common/asm/ASMBitLastSetU16.asm create mode 100644 src/VBox/Runtime/common/asm/ASMBitLastSetU32.asm create mode 100644 src/VBox/Runtime/common/asm/ASMBitLastSetU64.asm create mode 100644 src/VBox/Runtime/common/asm/ASMBitNextClear-generic.cpp create mode 100644 src/VBox/Runtime/common/asm/ASMBitNextClear.asm create mode 100644 src/VBox/Runtime/common/asm/ASMBitNextSet-generic.cpp create mode 100644 src/VBox/Runtime/common/asm/ASMBitNextSet.asm create mode 100644 src/VBox/Runtime/common/asm/ASMCpuId.asm create mode 100644 src/VBox/Runtime/common/asm/ASMCpuIdExSlow.asm create mode 100644 src/VBox/Runtime/common/asm/ASMCpuId_Idx_ECX.asm create mode 100644 src/VBox/Runtime/common/asm/ASMFxRstor.asm create mode 100644 src/VBox/Runtime/common/asm/ASMFxSave.asm create mode 100644 src/VBox/Runtime/common/asm/ASMGetFSBase.asm create mode 100644 src/VBox/Runtime/common/asm/ASMGetFlags.asm create mode 100644 src/VBox/Runtime/common/asm/ASMGetGDTR.asm create mode 100644 src/VBox/Runtime/common/asm/ASMGetGSBase.asm create mode 100644 src/VBox/Runtime/common/asm/ASMGetIDTR.asm create mode 100644 src/VBox/Runtime/common/asm/ASMGetIdtrLimit.asm create mode 100644 src/VBox/Runtime/common/asm/ASMGetLDTR.asm create mode 100644 src/VBox/Runtime/common/asm/ASMGetSegAttr.asm create mode 100644 src/VBox/Runtime/common/asm/ASMGetTR.asm create mode 100644 src/VBox/Runtime/common/asm/ASMGetXcr0.asm create mode 100644 src/VBox/Runtime/common/asm/ASMMemFill32-generic.cpp create mode 100644 src/VBox/Runtime/common/asm/ASMMemFirstMismatchingU8-generic.cpp create mode 100644 src/VBox/Runtime/common/asm/ASMMemFirstMismatchingU8.asm create mode 100644 src/VBox/Runtime/common/asm/ASMMemFirstNonZero-generic.cpp create mode 100644 src/VBox/Runtime/common/asm/ASMMemZero32-generic.cpp create mode 100644 src/VBox/Runtime/common/asm/ASMMemZeroPage-generic.cpp create mode 100644 src/VBox/Runtime/common/asm/ASMMultU32ByU32DivByU32.asm create mode 100644 src/VBox/Runtime/common/asm/ASMMultU64ByU32DivByU32-generic.cpp create mode 100644 src/VBox/Runtime/common/asm/ASMMultU64ByU32DivByU32.asm create mode 100644 src/VBox/Runtime/common/asm/ASMNopPause.asm create mode 100644 src/VBox/Runtime/common/asm/ASMRdMsrEx.asm create mode 100644 src/VBox/Runtime/common/asm/ASMSerializeInstruction-cpuid.asm create mode 100644 src/VBox/Runtime/common/asm/ASMSerializeInstruction-iret.asm create mode 100644 src/VBox/Runtime/common/asm/ASMSerializeInstruction-rdtscp.asm create mode 100644 src/VBox/Runtime/common/asm/ASMSetFSBase.asm create mode 100644 src/VBox/Runtime/common/asm/ASMSetFlags.asm create mode 100644 src/VBox/Runtime/common/asm/ASMSetGDTR.asm create mode 100644 src/VBox/Runtime/common/asm/ASMSetGSBase.asm create mode 100644 src/VBox/Runtime/common/asm/ASMSetIDTR.asm create mode 100644 src/VBox/Runtime/common/asm/ASMSetXcr0.asm create mode 100644 src/VBox/Runtime/common/asm/ASMWrMsr.asm create mode 100644 src/VBox/Runtime/common/asm/ASMWrMsrEx.asm create mode 100644 src/VBox/Runtime/common/asm/ASMXRstor.asm create mode 100644 src/VBox/Runtime/common/asm/ASMXSave.asm create mode 100644 src/VBox/Runtime/common/asm/Makefile.kup create mode 100644 src/VBox/Runtime/common/asm/asm-fake.cpp create mode 100644 src/VBox/Runtime/common/asn1/Makefile.kup create mode 100644 src/VBox/Runtime/common/asn1/asn1-basics.cpp create mode 100644 src/VBox/Runtime/common/asn1/asn1-cursor.cpp create mode 100644 src/VBox/Runtime/common/asn1/asn1-default-allocator.cpp create mode 100644 src/VBox/Runtime/common/asn1/asn1-dump.cpp create mode 100644 src/VBox/Runtime/common/asn1/asn1-efence-allocator.cpp create mode 100644 src/VBox/Runtime/common/asn1/asn1-encode.cpp create mode 100644 src/VBox/Runtime/common/asn1/asn1-safer-allocator.cpp create mode 100644 src/VBox/Runtime/common/asn1/asn1-ut-bitstring-decode.cpp create mode 100644 src/VBox/Runtime/common/asn1/asn1-ut-bitstring-template.h create mode 100644 src/VBox/Runtime/common/asn1/asn1-ut-bitstring.cpp create mode 100644 src/VBox/Runtime/common/asn1/asn1-ut-boolean-decode.cpp create mode 100644 src/VBox/Runtime/common/asn1/asn1-ut-boolean-template.h create mode 100644 src/VBox/Runtime/common/asn1/asn1-ut-boolean.cpp create mode 100644 src/VBox/Runtime/common/asn1/asn1-ut-core-decode.cpp create mode 100644 src/VBox/Runtime/common/asn1/asn1-ut-core-template.h create mode 100644 src/VBox/Runtime/common/asn1/asn1-ut-core.cpp create mode 100644 src/VBox/Runtime/common/asn1/asn1-ut-dyntype-decode.cpp create mode 100644 src/VBox/Runtime/common/asn1/asn1-ut-dyntype.cpp create mode 100644 src/VBox/Runtime/common/asn1/asn1-ut-integer-decode.cpp create mode 100644 src/VBox/Runtime/common/asn1/asn1-ut-integer-template.h create mode 100644 src/VBox/Runtime/common/asn1/asn1-ut-integer.cpp create mode 100644 src/VBox/Runtime/common/asn1/asn1-ut-null-decode.cpp create mode 100644 src/VBox/Runtime/common/asn1/asn1-ut-null.cpp create mode 100644 src/VBox/Runtime/common/asn1/asn1-ut-objid-decode.cpp create mode 100644 src/VBox/Runtime/common/asn1/asn1-ut-objid-template.h create mode 100644 src/VBox/Runtime/common/asn1/asn1-ut-objid.cpp create mode 100644 src/VBox/Runtime/common/asn1/asn1-ut-octetstring-decode.cpp create mode 100644 src/VBox/Runtime/common/asn1/asn1-ut-octetstring-template.h create mode 100644 src/VBox/Runtime/common/asn1/asn1-ut-octetstring.cpp create mode 100644 src/VBox/Runtime/common/asn1/asn1-ut-string-decode.cpp create mode 100644 src/VBox/Runtime/common/asn1/asn1-ut-string-template.h create mode 100644 src/VBox/Runtime/common/asn1/asn1-ut-string-template2.h create mode 100644 src/VBox/Runtime/common/asn1/asn1-ut-string.cpp create mode 100644 src/VBox/Runtime/common/asn1/asn1-ut-time-decode.cpp create mode 100644 src/VBox/Runtime/common/asn1/asn1-ut-time-template.h create mode 100644 src/VBox/Runtime/common/asn1/asn1-ut-time-template2.h create mode 100644 src/VBox/Runtime/common/asn1/asn1-ut-time.cpp create mode 100644 src/VBox/Runtime/common/asn1/oiddb.cfg create mode 100644 src/VBox/Runtime/common/asn1/oiddb2c.cpp create mode 100644 src/VBox/Runtime/common/checksum/Makefile.kup create mode 100644 src/VBox/Runtime/common/checksum/RTSha1Digest.cpp create mode 100644 src/VBox/Runtime/common/checksum/RTSha256Digest.cpp create mode 100644 src/VBox/Runtime/common/checksum/adler32.cpp create mode 100644 src/VBox/Runtime/common/checksum/alt-md2.cpp create mode 100644 src/VBox/Runtime/common/checksum/alt-md4.cpp create mode 100644 src/VBox/Runtime/common/checksum/alt-md5.cpp create mode 100644 src/VBox/Runtime/common/checksum/alt-sha1.cpp create mode 100644 src/VBox/Runtime/common/checksum/alt-sha256.cpp create mode 100644 src/VBox/Runtime/common/checksum/alt-sha3.cpp create mode 100644 src/VBox/Runtime/common/checksum/alt-sha512.cpp create mode 100644 src/VBox/Runtime/common/checksum/crc16ccitt.cpp create mode 100644 src/VBox/Runtime/common/checksum/crc32-zlib.cpp create mode 100644 src/VBox/Runtime/common/checksum/crc32.cpp create mode 100644 src/VBox/Runtime/common/checksum/crc32c.cpp create mode 100644 src/VBox/Runtime/common/checksum/crc64.cpp create mode 100644 src/VBox/Runtime/common/checksum/ipv4.cpp create mode 100644 src/VBox/Runtime/common/checksum/ipv6.cpp create mode 100644 src/VBox/Runtime/common/checksum/manifest-file.cpp create mode 100644 src/VBox/Runtime/common/checksum/manifest.cpp create mode 100644 src/VBox/Runtime/common/checksum/manifest2.cpp create mode 100644 src/VBox/Runtime/common/checksum/manifest3.cpp create mode 100644 src/VBox/Runtime/common/checksum/md2str.cpp create mode 100644 src/VBox/Runtime/common/checksum/md4str.cpp create mode 100644 src/VBox/Runtime/common/checksum/md5str.cpp create mode 100644 src/VBox/Runtime/common/checksum/openssl-md2.cpp create mode 100644 src/VBox/Runtime/common/checksum/openssl-md4.cpp create mode 100644 src/VBox/Runtime/common/checksum/openssl-md5.cpp create mode 100644 src/VBox/Runtime/common/checksum/openssl-sha1.cpp create mode 100644 src/VBox/Runtime/common/checksum/openssl-sha256.cpp create mode 100644 src/VBox/Runtime/common/checksum/openssl-sha3.cpp create mode 100644 src/VBox/Runtime/common/checksum/openssl-sha512.cpp create mode 100644 src/VBox/Runtime/common/checksum/sha1str.cpp create mode 100644 src/VBox/Runtime/common/checksum/sha224str.cpp create mode 100644 src/VBox/Runtime/common/checksum/sha256str.cpp create mode 100644 src/VBox/Runtime/common/checksum/sha384str.cpp create mode 100644 src/VBox/Runtime/common/checksum/sha512str.cpp create mode 100644 src/VBox/Runtime/common/checksum/sha512t224str.cpp create mode 100644 src/VBox/Runtime/common/checksum/sha512t256str.cpp create mode 100644 src/VBox/Runtime/common/compiler/vcc/atexit-vcc.cpp create mode 100644 src/VBox/Runtime/common/compiler/vcc/except-seh-vcc.cpp create mode 100644 src/VBox/Runtime/common/compiler/vcc/except-vcc.h create mode 100644 src/VBox/Runtime/common/compiler/vcc/except-x86-vcc-asm.asm create mode 100644 src/VBox/Runtime/common/compiler/vcc/except-x86-vcc.cpp create mode 100644 src/VBox/Runtime/common/compiler/vcc/fltused-vcc.cpp create mode 100644 src/VBox/Runtime/common/compiler/vcc/ftol2-vcc.asm create mode 100644 src/VBox/Runtime/common/compiler/vcc/guard-vcc.asm create mode 100644 src/VBox/Runtime/common/compiler/vcc/initializers-c-cpp-vcc.cpp create mode 100644 src/VBox/Runtime/common/compiler/vcc/loadcfg-vcc.c create mode 100644 src/VBox/Runtime/common/compiler/vcc/purecall-vcc.cpp create mode 100644 src/VBox/Runtime/common/compiler/vcc/stack-except-seh-vcc.cpp create mode 100644 src/VBox/Runtime/common/compiler/vcc/stack-except-vcc.cpp create mode 100644 src/VBox/Runtime/common/compiler/vcc/stack-probe-vcc.asm create mode 100644 src/VBox/Runtime/common/compiler/vcc/stack-vcc.asm create mode 100644 src/VBox/Runtime/common/compiler/vcc/stacksup-vcc.cpp create mode 100644 src/VBox/Runtime/common/compiler/vcc/tlsdir-vcc.c create mode 100644 src/VBox/Runtime/common/crypto/Makefile.kup create mode 100644 src/VBox/Runtime/common/crypto/RTCrPkcs5Pbkdf2Hmac-openssl.cpp create mode 100644 src/VBox/Runtime/common/crypto/RTCrRandBytes-openssl.cpp create mode 100644 src/VBox/Runtime/common/crypto/RTCrStoreCertAddFromJavaKeyStore.cpp create mode 100644 src/VBox/Runtime/common/crypto/RTCrStoreCertAddWantedFromFishingExpedition.cpp create mode 100644 src/VBox/Runtime/common/crypto/RTCrStoreCertExportAsPem.cpp create mode 100644 src/VBox/Runtime/common/crypto/RTCrStoreCreateSnapshotOfUserAndSystemTrustedCAsAndCerts.cpp create mode 100644 src/VBox/Runtime/common/crypto/cipher-openssl.cpp create mode 100644 src/VBox/Runtime/common/crypto/digest-builtin.cpp create mode 100644 src/VBox/Runtime/common/crypto/digest-core.cpp create mode 100644 src/VBox/Runtime/common/crypto/digest-vfs.cpp create mode 100644 src/VBox/Runtime/common/crypto/iprt-openssl.cpp create mode 100644 src/VBox/Runtime/common/crypto/key-create-rsa-openssl.cpp create mode 100644 src/VBox/Runtime/common/crypto/key-file.cpp create mode 100644 src/VBox/Runtime/common/crypto/key-internal.h create mode 100644 src/VBox/Runtime/common/crypto/key-openssl.cpp create mode 100644 src/VBox/Runtime/common/crypto/key.cpp create mode 100644 src/VBox/Runtime/common/crypto/pemfile-read.cpp create mode 100644 src/VBox/Runtime/common/crypto/pemfile-write.cpp create mode 100644 src/VBox/Runtime/common/crypto/pkcs7-asn1-decoder.cpp create mode 100644 src/VBox/Runtime/common/crypto/pkcs7-core.cpp create mode 100644 src/VBox/Runtime/common/crypto/pkcs7-file.cpp create mode 100644 src/VBox/Runtime/common/crypto/pkcs7-init.cpp create mode 100644 src/VBox/Runtime/common/crypto/pkcs7-internal.h create mode 100644 src/VBox/Runtime/common/crypto/pkcs7-sanity.cpp create mode 100644 src/VBox/Runtime/common/crypto/pkcs7-sign.cpp create mode 100644 src/VBox/Runtime/common/crypto/pkcs7-template.h create mode 100644 src/VBox/Runtime/common/crypto/pkcs7-verify.cpp create mode 100644 src/VBox/Runtime/common/crypto/pkcs8-asn1-decoder.cpp create mode 100644 src/VBox/Runtime/common/crypto/pkcs8-core.cpp create mode 100644 src/VBox/Runtime/common/crypto/pkcs8-init.cpp create mode 100644 src/VBox/Runtime/common/crypto/pkcs8-internal.h create mode 100644 src/VBox/Runtime/common/crypto/pkcs8-sanity.cpp create mode 100644 src/VBox/Runtime/common/crypto/pkcs8-template.h create mode 100644 src/VBox/Runtime/common/crypto/pkix-sign.cpp create mode 100644 src/VBox/Runtime/common/crypto/pkix-signature-builtin.cpp create mode 100644 src/VBox/Runtime/common/crypto/pkix-signature-builtin.h create mode 100644 src/VBox/Runtime/common/crypto/pkix-signature-core.cpp create mode 100644 src/VBox/Runtime/common/crypto/pkix-signature-ossl.cpp create mode 100644 src/VBox/Runtime/common/crypto/pkix-signature-rsa.cpp create mode 100644 src/VBox/Runtime/common/crypto/pkix-util.cpp create mode 100644 src/VBox/Runtime/common/crypto/pkix-verify.cpp create mode 100644 src/VBox/Runtime/common/crypto/rc4-openssl.cpp create mode 100644 src/VBox/Runtime/common/crypto/rsa-asn1-decoder.cpp create mode 100644 src/VBox/Runtime/common/crypto/rsa-core.cpp create mode 100644 src/VBox/Runtime/common/crypto/rsa-init.cpp create mode 100644 src/VBox/Runtime/common/crypto/rsa-internal.h create mode 100644 src/VBox/Runtime/common/crypto/rsa-sanity.cpp create mode 100644 src/VBox/Runtime/common/crypto/rsa-template.h create mode 100644 src/VBox/Runtime/common/crypto/spc-asn1-decoder.cpp create mode 100644 src/VBox/Runtime/common/crypto/spc-core.cpp create mode 100644 src/VBox/Runtime/common/crypto/spc-init.cpp create mode 100644 src/VBox/Runtime/common/crypto/spc-internal.h create mode 100644 src/VBox/Runtime/common/crypto/spc-sanity.cpp create mode 100644 src/VBox/Runtime/common/crypto/spc-template.h create mode 100644 src/VBox/Runtime/common/crypto/ssl-openssl.cpp create mode 100644 src/VBox/Runtime/common/crypto/store-cert-add-basic.cpp create mode 100644 src/VBox/Runtime/common/crypto/store-inmem.cpp create mode 100644 src/VBox/Runtime/common/crypto/store-internal.h create mode 100644 src/VBox/Runtime/common/crypto/store.cpp create mode 100644 src/VBox/Runtime/common/crypto/taf-asn1-decoder.cpp create mode 100644 src/VBox/Runtime/common/crypto/taf-core.cpp create mode 100644 src/VBox/Runtime/common/crypto/taf-init.cpp create mode 100644 src/VBox/Runtime/common/crypto/taf-internal.h create mode 100644 src/VBox/Runtime/common/crypto/taf-sanity.cpp create mode 100644 src/VBox/Runtime/common/crypto/taf-template.h create mode 100644 src/VBox/Runtime/common/crypto/tsp-asn1-decoder.cpp create mode 100644 src/VBox/Runtime/common/crypto/tsp-core.cpp create mode 100644 src/VBox/Runtime/common/crypto/tsp-init.cpp create mode 100644 src/VBox/Runtime/common/crypto/tsp-internal.h create mode 100644 src/VBox/Runtime/common/crypto/tsp-sanity.cpp create mode 100644 src/VBox/Runtime/common/crypto/tsp-template.h create mode 100644 src/VBox/Runtime/common/crypto/x509-asn1-decoder.cpp create mode 100644 src/VBox/Runtime/common/crypto/x509-certpaths.cpp create mode 100644 src/VBox/Runtime/common/crypto/x509-core.cpp create mode 100644 src/VBox/Runtime/common/crypto/x509-file.cpp create mode 100644 src/VBox/Runtime/common/crypto/x509-init.cpp create mode 100644 src/VBox/Runtime/common/crypto/x509-internal.h create mode 100644 src/VBox/Runtime/common/crypto/x509-sanity.cpp create mode 100644 src/VBox/Runtime/common/crypto/x509-template.h create mode 100644 src/VBox/Runtime/common/crypto/x509-verify.cpp create mode 100644 src/VBox/Runtime/common/dbg/Makefile.kup create mode 100644 src/VBox/Runtime/common/dbg/dbg.cpp create mode 100644 src/VBox/Runtime/common/dbg/dbgas.cpp create mode 100644 src/VBox/Runtime/common/dbg/dbgcfg.cpp create mode 100644 src/VBox/Runtime/common/dbg/dbgmod.cpp create mode 100644 src/VBox/Runtime/common/dbg/dbgmodcodeview.cpp create mode 100644 src/VBox/Runtime/common/dbg/dbgmodcontainer.cpp create mode 100644 src/VBox/Runtime/common/dbg/dbgmoddbghelp.cpp create mode 100644 src/VBox/Runtime/common/dbg/dbgmoddeferred.cpp create mode 100644 src/VBox/Runtime/common/dbg/dbgmoddwarf.cpp create mode 100644 src/VBox/Runtime/common/dbg/dbgmodexports.cpp create mode 100644 src/VBox/Runtime/common/dbg/dbgmodghidra.cpp create mode 100644 src/VBox/Runtime/common/dbg/dbgmodldr.cpp create mode 100644 src/VBox/Runtime/common/dbg/dbgmodmapsym.cpp create mode 100644 src/VBox/Runtime/common/dbg/dbgmodnm.cpp create mode 100644 src/VBox/Runtime/common/dbg/dbgstackdumpself-amd64-x86.asm create mode 100644 src/VBox/Runtime/common/dbg/dbgstackdumpself.cpp create mode 100644 src/VBox/Runtime/common/dvm/Makefile.kup create mode 100644 src/VBox/Runtime/common/dvm/dvm.cpp create mode 100644 src/VBox/Runtime/common/dvm/dvmbsdlabel.cpp create mode 100644 src/VBox/Runtime/common/dvm/dvmgpt.cpp create mode 100644 src/VBox/Runtime/common/dvm/dvmmbr.cpp create mode 100644 src/VBox/Runtime/common/dvm/dvmvfs.cpp create mode 100644 src/VBox/Runtime/common/efi/efiguid.cpp create mode 100644 src/VBox/Runtime/common/efi/efisignaturedb.cpp create mode 100644 src/VBox/Runtime/common/efi/efitime.cpp create mode 100644 src/VBox/Runtime/common/efi/efivarstorevfs.cpp create mode 100644 src/VBox/Runtime/common/err/Makefile.kup create mode 100644 src/VBox/Runtime/common/err/RTErrConvertFromErrno.cpp create mode 100644 src/VBox/Runtime/common/err/RTErrConvertToErrno.cpp create mode 100644 src/VBox/Runtime/common/err/errinfo-alloc.cpp create mode 100644 src/VBox/Runtime/common/err/errinfo.cpp create mode 100644 src/VBox/Runtime/common/err/errinfolog.cpp create mode 100644 src/VBox/Runtime/common/err/errmsg-sorter.cpp create mode 100644 src/VBox/Runtime/common/err/errmsg.cpp create mode 100644 src/VBox/Runtime/common/err/errmsg.sed create mode 100644 src/VBox/Runtime/common/err/errmsgcom.sed create mode 100644 src/VBox/Runtime/common/err/errmsgxpcom.cpp create mode 100644 src/VBox/Runtime/common/err/nocrt-strerror.cpp create mode 100644 src/VBox/Runtime/common/file/nocrt-close.cpp create mode 100644 src/VBox/Runtime/common/file/nocrt-dup.cpp create mode 100644 src/VBox/Runtime/common/file/nocrt-fstat.cpp create mode 100644 src/VBox/Runtime/common/file/nocrt-isatty.cpp create mode 100644 src/VBox/Runtime/common/file/nocrt-open.cpp create mode 100644 src/VBox/Runtime/common/file/nocrt-read.cpp create mode 100644 src/VBox/Runtime/common/fs/Makefile.kup create mode 100644 src/VBox/Runtime/common/fs/RTFsCmdLs.cpp create mode 100644 src/VBox/Runtime/common/fs/extvfs.cpp create mode 100644 src/VBox/Runtime/common/fs/fatvfs.cpp create mode 100644 src/VBox/Runtime/common/fs/isomaker.cpp create mode 100644 src/VBox/Runtime/common/fs/isomakercmd-man.xml create mode 100644 src/VBox/Runtime/common/fs/isomakercmd.cpp create mode 100644 src/VBox/Runtime/common/fs/isomakerimport.cpp create mode 100644 src/VBox/Runtime/common/fs/isovfs.cpp create mode 100644 src/VBox/Runtime/common/fs/ntfsvfs.cpp create mode 100644 src/VBox/Runtime/common/fs/xfsvfs.cpp create mode 100644 src/VBox/Runtime/common/fuzz/Makefile.kup create mode 100644 src/VBox/Runtime/common/fuzz/fuzz-config.cpp create mode 100644 src/VBox/Runtime/common/fuzz/fuzz-observer.cpp create mode 100644 src/VBox/Runtime/common/fuzz/fuzz-target-recorder.cpp create mode 100644 src/VBox/Runtime/common/fuzz/fuzz.cpp create mode 100644 src/VBox/Runtime/common/fuzz/fuzzclientcmd.cpp create mode 100644 src/VBox/Runtime/common/fuzz/fuzzmastercmd.cpp create mode 100644 src/VBox/Runtime/common/ioqueue/ioqueue-aiofile-provider.cpp create mode 100644 src/VBox/Runtime/common/ioqueue/ioqueue-stdfile-provider.cpp create mode 100644 src/VBox/Runtime/common/ioqueue/ioqueuebase.cpp create mode 100644 src/VBox/Runtime/common/ldr/Makefile.kup create mode 100644 src/VBox/Runtime/common/ldr/ldr.cpp create mode 100644 src/VBox/Runtime/common/ldr/ldrELF.cpp create mode 100644 src/VBox/Runtime/common/ldr/ldrELFRelocatable.cpp.h create mode 100644 src/VBox/Runtime/common/ldr/ldrEx.cpp create mode 100644 src/VBox/Runtime/common/ldr/ldrFile.cpp create mode 100644 src/VBox/Runtime/common/ldr/ldrLX.cpp create mode 100644 src/VBox/Runtime/common/ldr/ldrMachO.cpp create mode 100644 src/VBox/Runtime/common/ldr/ldrMemory.cpp create mode 100644 src/VBox/Runtime/common/ldr/ldrNative.cpp create mode 100644 src/VBox/Runtime/common/ldr/ldrPE.cpp create mode 100644 src/VBox/Runtime/common/ldr/ldrVfsFile.cpp create mode 100644 src/VBox/Runtime/common/log/Makefile.kup create mode 100644 src/VBox/Runtime/common/log/RTLogCreateEx.cpp create mode 100644 src/VBox/Runtime/common/log/RTLogSetR0ThreadNameF.cpp create mode 100644 src/VBox/Runtime/common/log/log-weak-assert.cpp create mode 100644 src/VBox/Runtime/common/log/log-weak-rel.cpp create mode 100644 src/VBox/Runtime/common/log/log-weak.cpp create mode 100644 src/VBox/Runtime/common/log/log.cpp create mode 100644 src/VBox/Runtime/common/log/logcom.cpp create mode 100644 src/VBox/Runtime/common/log/logellipsis.cpp create mode 100644 src/VBox/Runtime/common/log/logformat.cpp create mode 100644 src/VBox/Runtime/common/log/logrel.cpp create mode 100644 src/VBox/Runtime/common/log/logrelellipsis.cpp create mode 100644 src/VBox/Runtime/common/log/tracebuf.cpp create mode 100644 src/VBox/Runtime/common/log/tracedefault.cpp create mode 100644 src/VBox/Runtime/common/log/tracelogreader.cpp create mode 100644 src/VBox/Runtime/common/log/tracelogwriter.cpp create mode 100644 src/VBox/Runtime/common/math/Makefile.kup create mode 100644 src/VBox/Runtime/common/math/RTUInt128MulByU64.asm create mode 100644 src/VBox/Runtime/common/math/RTUInt128MulByU64Ex.asm create mode 100644 src/VBox/Runtime/common/math/__fpclassifyd.cpp create mode 100644 src/VBox/Runtime/common/math/__fpclassifyf.cpp create mode 100644 src/VBox/Runtime/common/math/__fpclassifyl.cpp create mode 100644 src/VBox/Runtime/common/math/__isfinite.cpp create mode 100644 src/VBox/Runtime/common/math/__isfinitef.cpp create mode 100644 src/VBox/Runtime/common/math/__isfinitel.cpp create mode 100644 src/VBox/Runtime/common/math/__isinff.cpp create mode 100644 src/VBox/Runtime/common/math/__isinfl.cpp create mode 100644 src/VBox/Runtime/common/math/__isnanl.cpp create mode 100644 src/VBox/Runtime/common/math/__isnormal.cpp create mode 100644 src/VBox/Runtime/common/math/__isnormalf.cpp create mode 100644 src/VBox/Runtime/common/math/__isnormall.cpp create mode 100644 src/VBox/Runtime/common/math/__signbit.cpp create mode 100644 src/VBox/Runtime/common/math/__signbitf.cpp create mode 100644 src/VBox/Runtime/common/math/__signbitl.cpp create mode 100644 src/VBox/Runtime/common/math/atan.asm create mode 100644 src/VBox/Runtime/common/math/atan2.asm create mode 100644 src/VBox/Runtime/common/math/atan2f.asm create mode 100644 src/VBox/Runtime/common/math/atanf.asm create mode 100644 src/VBox/Runtime/common/math/bignum-amd64-x86.asm create mode 100644 src/VBox/Runtime/common/math/bignum.cpp create mode 100644 src/VBox/Runtime/common/math/ceil.asm create mode 100644 src/VBox/Runtime/common/math/ceilf.asm create mode 100644 src/VBox/Runtime/common/math/ceill.asm create mode 100644 src/VBox/Runtime/common/math/consts.c create mode 100644 src/VBox/Runtime/common/math/copysign.cpp create mode 100644 src/VBox/Runtime/common/math/copysignf.cpp create mode 100644 src/VBox/Runtime/common/math/copysignl.cpp create mode 100644 src/VBox/Runtime/common/math/cos.asm create mode 100644 src/VBox/Runtime/common/math/cosf.asm create mode 100644 src/VBox/Runtime/common/math/cosl.asm create mode 100644 src/VBox/Runtime/common/math/exp.asm create mode 100644 src/VBox/Runtime/common/math/exp2.asm create mode 100644 src/VBox/Runtime/common/math/exp2f.asm create mode 100644 src/VBox/Runtime/common/math/expf.asm create mode 100644 src/VBox/Runtime/common/math/fabs.asm create mode 100644 src/VBox/Runtime/common/math/fabsf.asm create mode 100644 src/VBox/Runtime/common/math/fabsl.asm create mode 100644 src/VBox/Runtime/common/math/feclearexcept.asm create mode 100644 src/VBox/Runtime/common/math/fedisableexcept.asm create mode 100644 src/VBox/Runtime/common/math/feenableexcept.asm create mode 100644 src/VBox/Runtime/common/math/fegetenv.asm create mode 100644 src/VBox/Runtime/common/math/fegetexcept.asm create mode 100644 src/VBox/Runtime/common/math/fegetexceptflag.asm create mode 100644 src/VBox/Runtime/common/math/fegetround.asm create mode 100644 src/VBox/Runtime/common/math/fegetx87precision.asm create mode 100644 src/VBox/Runtime/common/math/feholdexcept.asm create mode 100644 src/VBox/Runtime/common/math/feraiseexcept.asm create mode 100644 src/VBox/Runtime/common/math/fesetenv.asm create mode 100644 src/VBox/Runtime/common/math/fesetexceptflag.asm create mode 100644 src/VBox/Runtime/common/math/fesetround.asm create mode 100644 src/VBox/Runtime/common/math/fesetx87precision.asm create mode 100644 src/VBox/Runtime/common/math/fetestexcept.asm create mode 100644 src/VBox/Runtime/common/math/feupdateenv.asm create mode 100644 src/VBox/Runtime/common/math/floor.asm create mode 100644 src/VBox/Runtime/common/math/floorf.asm create mode 100644 src/VBox/Runtime/common/math/floorl.asm create mode 100644 src/VBox/Runtime/common/math/fma-asm.asm create mode 100644 src/VBox/Runtime/common/math/fma.cpp create mode 100644 src/VBox/Runtime/common/math/fmaf-asm.asm create mode 100644 src/VBox/Runtime/common/math/fmaf.cpp create mode 100644 src/VBox/Runtime/common/math/fmax.cpp create mode 100644 src/VBox/Runtime/common/math/fmaxf.cpp create mode 100644 src/VBox/Runtime/common/math/fmaxl.cpp create mode 100644 src/VBox/Runtime/common/math/fmin.cpp create mode 100644 src/VBox/Runtime/common/math/fminf.cpp create mode 100644 src/VBox/Runtime/common/math/fminl.cpp create mode 100644 src/VBox/Runtime/common/math/frexp.cpp create mode 100644 src/VBox/Runtime/common/math/frexpf.cpp create mode 100644 src/VBox/Runtime/common/math/frexpl.cpp create mode 100644 src/VBox/Runtime/common/math/gcc/Makefile.kup create mode 100644 src/VBox/Runtime/common/math/gcc/adddi3.c create mode 100644 src/VBox/Runtime/common/math/gcc/anddi3.c create mode 100644 src/VBox/Runtime/common/math/gcc/ashldi3.c create mode 100644 src/VBox/Runtime/common/math/gcc/ashrdi3.c create mode 100644 src/VBox/Runtime/common/math/gcc/cmpdi2.c create mode 100644 src/VBox/Runtime/common/math/gcc/divdi3.c create mode 100644 src/VBox/Runtime/common/math/gcc/divmoddi4.c create mode 100644 src/VBox/Runtime/common/math/gcc/iordi3.c create mode 100644 src/VBox/Runtime/common/math/gcc/lshldi3.c create mode 100644 src/VBox/Runtime/common/math/gcc/lshrdi3.c create mode 100644 src/VBox/Runtime/common/math/gcc/moddi3.c create mode 100644 src/VBox/Runtime/common/math/gcc/muldi3.c create mode 100644 src/VBox/Runtime/common/math/gcc/negdi2.c create mode 100644 src/VBox/Runtime/common/math/gcc/notdi2.c create mode 100644 src/VBox/Runtime/common/math/gcc/qdivrem.c create mode 100644 src/VBox/Runtime/common/math/gcc/quad.h create mode 100644 src/VBox/Runtime/common/math/gcc/subdi3.c create mode 100644 src/VBox/Runtime/common/math/gcc/ucmpdi2.c create mode 100644 src/VBox/Runtime/common/math/gcc/udivdi3.c create mode 100644 src/VBox/Runtime/common/math/gcc/udivmoddi4.c create mode 100644 src/VBox/Runtime/common/math/gcc/umoddi3.c create mode 100644 src/VBox/Runtime/common/math/gcc/xordi3.c create mode 100644 src/VBox/Runtime/common/math/isinf.cpp create mode 100644 src/VBox/Runtime/common/math/isnan.cpp create mode 100644 src/VBox/Runtime/common/math/isnanf.cpp create mode 100644 src/VBox/Runtime/common/math/ldexp.asm create mode 100644 src/VBox/Runtime/common/math/ldexpf.asm create mode 100644 src/VBox/Runtime/common/math/ldexpl.asm create mode 100644 src/VBox/Runtime/common/math/llrint.asm create mode 100644 src/VBox/Runtime/common/math/llrintf.asm create mode 100644 src/VBox/Runtime/common/math/llrintl.asm create mode 100644 src/VBox/Runtime/common/math/llround.cpp create mode 100644 src/VBox/Runtime/common/math/llroundf.cpp create mode 100644 src/VBox/Runtime/common/math/llroundl.cpp create mode 100644 src/VBox/Runtime/common/math/log.asm create mode 100644 src/VBox/Runtime/common/math/log2.asm create mode 100644 src/VBox/Runtime/common/math/log2f.asm create mode 100644 src/VBox/Runtime/common/math/logf.asm create mode 100644 src/VBox/Runtime/common/math/logl.asm create mode 100644 src/VBox/Runtime/common/math/lrint.asm create mode 100644 src/VBox/Runtime/common/math/lrintf.asm create mode 100644 src/VBox/Runtime/common/math/lrintl.asm create mode 100644 src/VBox/Runtime/common/math/lround.cpp create mode 100644 src/VBox/Runtime/common/math/lroundf.cpp create mode 100644 src/VBox/Runtime/common/math/lroundl.cpp create mode 100644 src/VBox/Runtime/common/math/nocrt-abs.cpp create mode 100644 src/VBox/Runtime/common/math/nocrt-labs.cpp create mode 100644 src/VBox/Runtime/common/math/nocrt-llabs.cpp create mode 100644 src/VBox/Runtime/common/math/pow.asm create mode 100644 src/VBox/Runtime/common/math/powcore.asm create mode 100644 src/VBox/Runtime/common/math/powf.asm create mode 100644 src/VBox/Runtime/common/math/remainder.asm create mode 100644 src/VBox/Runtime/common/math/remainderf.asm create mode 100644 src/VBox/Runtime/common/math/remainderl.asm create mode 100644 src/VBox/Runtime/common/math/rint.asm create mode 100644 src/VBox/Runtime/common/math/rintf.asm create mode 100644 src/VBox/Runtime/common/math/round.cpp create mode 100644 src/VBox/Runtime/common/math/roundf.cpp create mode 100644 src/VBox/Runtime/common/math/roundl.cpp create mode 100644 src/VBox/Runtime/common/math/rtNoCrtHasSse.asm create mode 100644 src/VBox/Runtime/common/math/sin.asm create mode 100644 src/VBox/Runtime/common/math/sincore.asm create mode 100644 src/VBox/Runtime/common/math/sinf.asm create mode 100644 src/VBox/Runtime/common/math/sinl.asm create mode 100644 src/VBox/Runtime/common/math/sqrt.asm create mode 100644 src/VBox/Runtime/common/math/sqrtf.asm create mode 100644 src/VBox/Runtime/common/math/tan.asm create mode 100644 src/VBox/Runtime/common/math/tanf.asm create mode 100644 src/VBox/Runtime/common/math/tanl.asm create mode 100644 src/VBox/Runtime/common/math/trunc.asm create mode 100644 src/VBox/Runtime/common/math/truncf.asm create mode 100644 src/VBox/Runtime/common/math/truncl.asm create mode 100644 src/VBox/Runtime/common/math/watcom/I8D-x86-32.asm create mode 100644 src/VBox/Runtime/common/math/watcom/RTWatcomUInt64Div.c create mode 100644 src/VBox/Runtime/common/math/watcom/U8D-x86-32.asm create mode 100644 src/VBox/Runtime/common/math/watcom/U8LS-x86-32.asm create mode 100644 src/VBox/Runtime/common/math/watcom/U8M-I8M-x86-32.asm create mode 100644 src/VBox/Runtime/common/math/watcom/U8RS-x86-32.asm create mode 100644 src/VBox/Runtime/common/misc/Makefile.kup create mode 100644 src/VBox/Runtime/common/misc/RTAssertMsg1Weak.cpp create mode 100644 src/VBox/Runtime/common/misc/RTAssertMsg2.cpp create mode 100644 src/VBox/Runtime/common/misc/RTAssertMsg2Add.cpp create mode 100644 src/VBox/Runtime/common/misc/RTAssertMsg2AddWeak.cpp create mode 100644 src/VBox/Runtime/common/misc/RTAssertMsg2AddWeakV.cpp create mode 100644 src/VBox/Runtime/common/misc/RTAssertMsg2Weak.cpp create mode 100644 src/VBox/Runtime/common/misc/RTAssertMsg2WeakV.cpp create mode 100644 src/VBox/Runtime/common/misc/RTFileModeToFlags.cpp create mode 100644 src/VBox/Runtime/common/misc/RTFileOpenF.cpp create mode 100644 src/VBox/Runtime/common/misc/RTFileOpenV.cpp create mode 100644 src/VBox/Runtime/common/misc/RTMemWipeThoroughly.cpp create mode 100644 src/VBox/Runtime/common/misc/RTSystemFirmwareTypeName.cpp create mode 100644 src/VBox/Runtime/common/misc/RTSystemIsInsideVM-amd64-x86.cpp create mode 100644 src/VBox/Runtime/common/misc/assert.cpp create mode 100644 src/VBox/Runtime/common/misc/buildconfig.cpp create mode 100644 src/VBox/Runtime/common/misc/cidr.cpp create mode 100644 src/VBox/Runtime/common/misc/circbuf.cpp create mode 100644 src/VBox/Runtime/common/misc/expreval.cpp create mode 100644 src/VBox/Runtime/common/misc/getopt.cpp create mode 100644 src/VBox/Runtime/common/misc/getoptargv.cpp create mode 100644 src/VBox/Runtime/common/misc/handle.cpp create mode 100644 src/VBox/Runtime/common/misc/handletable.cpp create mode 100644 src/VBox/Runtime/common/misc/handletable.h create mode 100644 src/VBox/Runtime/common/misc/handletablectx.cpp create mode 100644 src/VBox/Runtime/common/misc/handletablesimple.cpp create mode 100644 src/VBox/Runtime/common/misc/inifile.cpp create mode 100644 src/VBox/Runtime/common/misc/json.cpp create mode 100644 src/VBox/Runtime/common/misc/lockvalidator.cpp create mode 100644 src/VBox/Runtime/common/misc/message.cpp create mode 100644 src/VBox/Runtime/common/misc/messagerefentry.cpp create mode 100644 src/VBox/Runtime/common/misc/once.cpp create mode 100644 src/VBox/Runtime/common/misc/req.cpp create mode 100644 src/VBox/Runtime/common/misc/reqpool.cpp create mode 100644 src/VBox/Runtime/common/misc/reqqueue.cpp create mode 100644 src/VBox/Runtime/common/misc/sanity-c.c create mode 100644 src/VBox/Runtime/common/misc/sanity-cpp.cpp create mode 100644 src/VBox/Runtime/common/misc/sanity.h create mode 100644 src/VBox/Runtime/common/misc/semspingpong.cpp create mode 100644 src/VBox/Runtime/common/misc/setjmp.asm create mode 100644 src/VBox/Runtime/common/misc/sg.cpp create mode 100644 src/VBox/Runtime/common/misc/term.cpp create mode 100644 src/VBox/Runtime/common/misc/thread.cpp create mode 100644 src/VBox/Runtime/common/misc/uri.cpp create mode 100644 src/VBox/Runtime/common/misc/zero-alt.S create mode 100644 src/VBox/Runtime/common/misc/zero.asm create mode 100644 src/VBox/Runtime/common/misc/zero.cpp create mode 100644 src/VBox/Runtime/common/net/Makefile.kup create mode 100644 src/VBox/Runtime/common/net/macstr.cpp create mode 100644 src/VBox/Runtime/common/net/netaddrstr.cpp create mode 100644 src/VBox/Runtime/common/net/netaddrstr2.cpp create mode 100644 src/VBox/Runtime/common/path/Makefile.kup create mode 100644 src/VBox/Runtime/common/path/RTPathAbsDup.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathAbsEx.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathAbsExDup.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathAppend.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathAppendEx.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathAppendEx.cpp.h create mode 100644 src/VBox/Runtime/common/path/RTPathCalcRelative.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathChangeToDosSlashes.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathChangeToUnixSlashes.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathCopyComponents.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathCountComponents.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathEnsureTrailingSeparator.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathExt.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathFilename.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathFilenameUtf16.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathFindCommon.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathFindCommon.cpp.h create mode 100644 src/VBox/Runtime/common/path/RTPathGlob.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathHasExt.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathHasPath.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathJoin.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathJoinA.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathJoinEx.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathParentLength.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathParentLength.cpp.h create mode 100644 src/VBox/Runtime/common/path/RTPathParse.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathParse.cpp.h create mode 100644 src/VBox/Runtime/common/path/RTPathParseSimple.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathParsedReassemble.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathPurgeFilename.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathRealDup.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathRmCmd.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathSkipRootSpec.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathSplit.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathSplitA.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathSplitReassemble.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathStartsWithRoot.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathStripExt.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathStripFilename.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathStripTrailingSlash.cpp create mode 100644 src/VBox/Runtime/common/path/RTPathTraverseList.cpp create mode 100644 src/VBox/Runtime/common/path/comparepaths.cpp create mode 100644 src/VBox/Runtime/common/path/nocrt-access.cpp create mode 100644 src/VBox/Runtime/common/path/nocrt-unlink.cpp create mode 100644 src/VBox/Runtime/common/path/rtPathRootSpecLen.cpp create mode 100644 src/VBox/Runtime/common/path/rtPathVolumeSpecLen.cpp create mode 100644 src/VBox/Runtime/common/path/rtpath-expand-template.cpp.h create mode 100644 src/VBox/Runtime/common/path/rtpath-root-length-template.cpp.h create mode 100644 src/VBox/Runtime/common/rand/Makefile.kup create mode 100644 src/VBox/Runtime/common/rand/nocrt-rand.cpp create mode 100644 src/VBox/Runtime/common/rand/rand.cpp create mode 100644 src/VBox/Runtime/common/rand/randadv.cpp create mode 100644 src/VBox/Runtime/common/rand/randparkmiller.cpp create mode 100644 src/VBox/Runtime/common/rest/Makefile.kup create mode 100644 src/VBox/Runtime/common/rest/RTCRestAnyObject.cpp create mode 100644 src/VBox/Runtime/common/rest/RTCRestArrayBase.cpp create mode 100644 src/VBox/Runtime/common/rest/RTCRestClientApiBase.cpp create mode 100644 src/VBox/Runtime/common/rest/RTCRestClientApiBaseOci.cpp create mode 100644 src/VBox/Runtime/common/rest/RTCRestClientRequestBase.cpp create mode 100644 src/VBox/Runtime/common/rest/RTCRestClientResponseBase.cpp create mode 100644 src/VBox/Runtime/common/rest/RTCRestJsonPrimaryCursor.cpp create mode 100644 src/VBox/Runtime/common/rest/RTCRestOutputBase.cpp create mode 100644 src/VBox/Runtime/common/rest/RTCRestOutputPrettyBase.cpp create mode 100644 src/VBox/Runtime/common/rest/RTCRestOutputPrettyToString.cpp create mode 100644 src/VBox/Runtime/common/rest/RTCRestOutputToString.cpp create mode 100644 src/VBox/Runtime/common/rest/RTCRestStringMapBase.cpp create mode 100644 src/VBox/Runtime/common/rest/rest-binary.cpp create mode 100644 src/VBox/Runtime/common/rest/rest-primary-object-types.cpp create mode 100644 src/VBox/Runtime/common/sort/Makefile.kup create mode 100644 src/VBox/Runtime/common/sort/RTSortApvIsSorted.cpp create mode 100644 src/VBox/Runtime/common/sort/RTSortIsSorted.cpp create mode 100644 src/VBox/Runtime/common/sort/nocrt-bsearch.cpp create mode 100644 src/VBox/Runtime/common/sort/nocrt-qsort.cpp create mode 100644 src/VBox/Runtime/common/sort/nocrt-qsort_r.cpp create mode 100644 src/VBox/Runtime/common/sort/shellsort.cpp create mode 100644 src/VBox/Runtime/common/string/Makefile.kup create mode 100644 src/VBox/Runtime/common/string/RTStrCat.cpp create mode 100644 src/VBox/Runtime/common/string/RTStrCatEx.cpp create mode 100644 src/VBox/Runtime/common/string/RTStrCatP.cpp create mode 100644 src/VBox/Runtime/common/string/RTStrCatPEx.cpp create mode 100644 src/VBox/Runtime/common/string/RTStrCmp.cpp create mode 100644 src/VBox/Runtime/common/string/RTStrCopy.cpp create mode 100644 src/VBox/Runtime/common/string/RTStrCopyEx.cpp create mode 100644 src/VBox/Runtime/common/string/RTStrCopyP.cpp create mode 100644 src/VBox/Runtime/common/string/RTStrCopyPEx.cpp create mode 100644 src/VBox/Runtime/common/string/RTStrEnd.asm create mode 100644 src/VBox/Runtime/common/string/RTStrEnd.cpp create mode 100644 src/VBox/Runtime/common/string/RTStrFormat.cpp create mode 100644 src/VBox/Runtime/common/string/RTStrICmpAscii.cpp create mode 100644 src/VBox/Runtime/common/string/RTStrIStartsWith.cpp create mode 100644 src/VBox/Runtime/common/string/RTStrMemFind32.asm create mode 100644 src/VBox/Runtime/common/string/RTStrMemFind32.cpp create mode 100644 src/VBox/Runtime/common/string/RTStrNCmp.cpp create mode 100644 src/VBox/Runtime/common/string/RTStrNICmpAscii.cpp create mode 100644 src/VBox/Runtime/common/string/RTStrNLen.cpp create mode 100644 src/VBox/Runtime/common/string/RTStrNLenEx.cpp create mode 100644 src/VBox/Runtime/common/string/RTStrPrintHexBytes.cpp create mode 100644 src/VBox/Runtime/common/string/RTStrSplit.cpp create mode 100644 src/VBox/Runtime/common/string/RTStrStartsWith.cpp create mode 100644 src/VBox/Runtime/common/string/RTStrStr.cpp create mode 100644 src/VBox/Runtime/common/string/RTUtf16Cat.cpp create mode 100644 src/VBox/Runtime/common/string/RTUtf16CatAscii.cpp create mode 100644 src/VBox/Runtime/common/string/RTUtf16Chr.cpp create mode 100644 src/VBox/Runtime/common/string/RTUtf16CmpAscii.cpp create mode 100644 src/VBox/Runtime/common/string/RTUtf16Copy.cpp create mode 100644 src/VBox/Runtime/common/string/RTUtf16CopyAscii.cpp create mode 100644 src/VBox/Runtime/common/string/RTUtf16CopyEx.cpp create mode 100644 src/VBox/Runtime/common/string/RTUtf16End.cpp create mode 100644 src/VBox/Runtime/common/string/RTUtf16FindAscii.cpp create mode 100644 src/VBox/Runtime/common/string/RTUtf16ICmpAscii.cpp create mode 100644 src/VBox/Runtime/common/string/RTUtf16NCmp.cpp create mode 100644 src/VBox/Runtime/common/string/RTUtf16NCmpAscii.cpp create mode 100644 src/VBox/Runtime/common/string/RTUtf16NCmpUtf8.cpp create mode 100644 src/VBox/Runtime/common/string/RTUtf16NICmpAscii.cpp create mode 100644 src/VBox/Runtime/common/string/RTUtf16NLen.cpp create mode 100644 src/VBox/Runtime/common/string/RTUtf16NLenEx.cpp create mode 100644 src/VBox/Runtime/common/string/RTUtf16PrintHexBytes.cpp create mode 100644 src/VBox/Runtime/common/string/atoi.cpp create mode 100644 src/VBox/Runtime/common/string/base64-utf16.cpp create mode 100644 src/VBox/Runtime/common/string/base64.cpp create mode 100644 src/VBox/Runtime/common/string/base64.h create mode 100644 src/VBox/Runtime/common/string/bzero.asm create mode 100644 src/VBox/Runtime/common/string/memchr.asm create mode 100644 src/VBox/Runtime/common/string/memchr.cpp create mode 100644 src/VBox/Runtime/common/string/memcmp.asm create mode 100644 src/VBox/Runtime/common/string/memcmp.cpp create mode 100644 src/VBox/Runtime/common/string/memcpy.asm create mode 100644 src/VBox/Runtime/common/string/memcpy.cpp create mode 100644 src/VBox/Runtime/common/string/memmove.asm create mode 100644 src/VBox/Runtime/common/string/mempcpy.asm create mode 100644 src/VBox/Runtime/common/string/mempcpy.cpp create mode 100644 src/VBox/Runtime/common/string/memrchr.asm create mode 100644 src/VBox/Runtime/common/string/memrchr.cpp create mode 100644 src/VBox/Runtime/common/string/memset.asm create mode 100644 src/VBox/Runtime/common/string/memset.cpp create mode 100644 src/VBox/Runtime/common/string/ministring.cpp create mode 100644 src/VBox/Runtime/common/string/nocrt-atof.cpp create mode 100644 src/VBox/Runtime/common/string/nocrt-scprintf.cpp create mode 100644 src/VBox/Runtime/common/string/nocrt-snprintf.cpp create mode 100644 src/VBox/Runtime/common/string/nocrt-sscanf.cpp create mode 100644 src/VBox/Runtime/common/string/nocrt-strdup.cpp create mode 100644 src/VBox/Runtime/common/string/nocrt-stricmp.cpp create mode 100644 src/VBox/Runtime/common/string/nocrt-strtod.cpp create mode 100644 src/VBox/Runtime/common/string/nocrt-strtok.cpp create mode 100644 src/VBox/Runtime/common/string/nocrt-strtol.cpp create mode 100644 src/VBox/Runtime/common/string/nocrt-strtoll.cpp create mode 100644 src/VBox/Runtime/common/string/nocrt-strtoul.cpp create mode 100644 src/VBox/Runtime/common/string/nocrt-strtoull.cpp create mode 100644 src/VBox/Runtime/common/string/nocrt-vscprintf.cpp create mode 100644 src/VBox/Runtime/common/string/nocrt-vsnprintf.cpp create mode 100644 src/VBox/Runtime/common/string/nocrt-vsscanf.cpp create mode 100644 src/VBox/Runtime/common/string/simplepattern.cpp create mode 100644 src/VBox/Runtime/common/string/straprintf.cpp create mode 100644 src/VBox/Runtime/common/string/strcache.cpp create mode 100644 src/VBox/Runtime/common/string/strcat.cpp create mode 100644 src/VBox/Runtime/common/string/strchr.asm create mode 100644 src/VBox/Runtime/common/string/strchr.cpp create mode 100644 src/VBox/Runtime/common/string/strcmp.asm create mode 100644 src/VBox/Runtime/common/string/strcpy.asm create mode 100644 src/VBox/Runtime/common/string/strcpy.cpp create mode 100644 src/VBox/Runtime/common/string/strcspn.cpp create mode 100644 src/VBox/Runtime/common/string/strformat.cpp create mode 100644 src/VBox/Runtime/common/string/strformatfloat.cpp create mode 100644 src/VBox/Runtime/common/string/strformatnum.cpp create mode 100644 src/VBox/Runtime/common/string/strformatrt.cpp create mode 100644 src/VBox/Runtime/common/string/strformattype.cpp create mode 100644 src/VBox/Runtime/common/string/strhash1.cpp create mode 100644 src/VBox/Runtime/common/string/stringalloc.cpp create mode 100644 src/VBox/Runtime/common/string/strlen.asm create mode 100644 src/VBox/Runtime/common/string/strlen.cpp create mode 100644 src/VBox/Runtime/common/string/strncat.cpp create mode 100644 src/VBox/Runtime/common/string/strncmp.asm create mode 100644 src/VBox/Runtime/common/string/strncmp.cpp create mode 100644 src/VBox/Runtime/common/string/strncpy.asm create mode 100644 src/VBox/Runtime/common/string/strnlen.cpp create mode 100644 src/VBox/Runtime/common/string/strpbrk.cpp create mode 100644 src/VBox/Runtime/common/string/strprintf-ellipsis.cpp create mode 100644 src/VBox/Runtime/common/string/strprintf.cpp create mode 100644 src/VBox/Runtime/common/string/strprintf2-ellipsis.cpp create mode 100644 src/VBox/Runtime/common/string/strprintf2.cpp create mode 100644 src/VBox/Runtime/common/string/strrchr.cpp create mode 100644 src/VBox/Runtime/common/string/strspace.cpp create mode 100644 src/VBox/Runtime/common/string/strstr.cpp create mode 100644 src/VBox/Runtime/common/string/strstrip.cpp create mode 100644 src/VBox/Runtime/common/string/strtofloat.cpp create mode 100644 src/VBox/Runtime/common/string/strtok_r.cpp create mode 100644 src/VBox/Runtime/common/string/strtonum.cpp create mode 100644 src/VBox/Runtime/common/string/strversion.cpp create mode 100644 src/VBox/Runtime/common/string/uni.cpp create mode 100644 src/VBox/Runtime/common/string/unidata-flags.cpp create mode 100644 src/VBox/Runtime/common/string/unidata-lower.cpp create mode 100644 src/VBox/Runtime/common/string/unidata-upper.cpp create mode 100644 src/VBox/Runtime/common/string/uniread.cpp create mode 100644 src/VBox/Runtime/common/string/utf-16-case.cpp create mode 100644 src/VBox/Runtime/common/string/utf-16-latin-1.cpp create mode 100644 src/VBox/Runtime/common/string/utf-16-printf.cpp create mode 100644 src/VBox/Runtime/common/string/utf-16.cpp create mode 100644 src/VBox/Runtime/common/string/utf-8-case.cpp create mode 100644 src/VBox/Runtime/common/string/utf-8-case2.cpp create mode 100644 src/VBox/Runtime/common/string/utf-8.cpp create mode 100644 src/VBox/Runtime/common/string/watcom/bzero.asm create mode 100644 src/VBox/Runtime/common/string/watcom/memchr.asm create mode 100644 src/VBox/Runtime/common/string/watcom/memcmp.asm create mode 100644 src/VBox/Runtime/common/string/watcom/memcpy.asm create mode 100644 src/VBox/Runtime/common/string/watcom/memmove.asm create mode 100644 src/VBox/Runtime/common/string/watcom/mempcpy.asm create mode 100644 src/VBox/Runtime/common/string/watcom/memrchr.asm create mode 100644 src/VBox/Runtime/common/string/watcom/memset.asm create mode 100644 src/VBox/Runtime/common/string/watcom/strchr.asm create mode 100644 src/VBox/Runtime/common/string/watcom/strcmp.asm create mode 100644 src/VBox/Runtime/common/string/watcom/strcpy.asm create mode 100644 src/VBox/Runtime/common/string/watcom/strlen.asm create mode 100644 src/VBox/Runtime/common/string/watcom/strncmp.asm create mode 100644 src/VBox/Runtime/common/string/watcom/strncpy.asm create mode 100644 src/VBox/Runtime/common/string/wcslen.asm create mode 100644 src/VBox/Runtime/common/table/Makefile.kup create mode 100644 src/VBox/Runtime/common/table/avl_Base.cpp.h create mode 100644 src/VBox/Runtime/common/table/avl_Destroy.cpp.h create mode 100644 src/VBox/Runtime/common/table/avl_DoWithAll.cpp.h create mode 100644 src/VBox/Runtime/common/table/avl_Enum.cpp.h create mode 100644 src/VBox/Runtime/common/table/avl_Get.cpp.h create mode 100644 src/VBox/Runtime/common/table/avl_GetBestFit.cpp.h create mode 100644 src/VBox/Runtime/common/table/avl_Range.cpp.h create mode 100644 src/VBox/Runtime/common/table/avl_RemoveBestFit.cpp.h create mode 100644 src/VBox/Runtime/common/table/avl_RemoveNode.cpp.h create mode 100644 src/VBox/Runtime/common/table/avlgcphys.cpp create mode 100644 src/VBox/Runtime/common/table/avlgcptr.cpp create mode 100644 src/VBox/Runtime/common/table/avlhcphys.cpp create mode 100644 src/VBox/Runtime/common/table/avllu32.cpp create mode 100644 src/VBox/Runtime/common/table/avlogcphys.cpp create mode 100644 src/VBox/Runtime/common/table/avlogcptr.cpp create mode 100644 src/VBox/Runtime/common/table/avlohcphys.cpp create mode 100644 src/VBox/Runtime/common/table/avloioport.cpp create mode 100644 src/VBox/Runtime/common/table/avlou32.cpp create mode 100644 src/VBox/Runtime/common/table/avlpv.cpp create mode 100644 src/VBox/Runtime/common/table/avlrfoff.cpp create mode 100644 src/VBox/Runtime/common/table/avlrgcptr.cpp create mode 100644 src/VBox/Runtime/common/table/avlrogcphys.cpp create mode 100644 src/VBox/Runtime/common/table/avlrogcptr.cpp create mode 100644 src/VBox/Runtime/common/table/avlroioport.cpp create mode 100644 src/VBox/Runtime/common/table/avlroogcptr.cpp create mode 100644 src/VBox/Runtime/common/table/avlrpv.cpp create mode 100644 src/VBox/Runtime/common/table/avlru64.cpp create mode 100644 src/VBox/Runtime/common/table/avlruintptr.cpp create mode 100644 src/VBox/Runtime/common/table/avlu32.cpp create mode 100644 src/VBox/Runtime/common/table/avlu64.cpp create mode 100644 src/VBox/Runtime/common/table/avluintptr.cpp create mode 100644 src/VBox/Runtime/common/table/avlul.cpp create mode 100644 src/VBox/Runtime/common/table/table.cpp create mode 100644 src/VBox/Runtime/common/time/Makefile.kup create mode 100644 src/VBox/Runtime/common/time/RTTimeFormatDurationEx.cpp create mode 100644 src/VBox/Runtime/common/time/time.cpp create mode 100644 src/VBox/Runtime/common/time/timeprog.cpp create mode 100644 src/VBox/Runtime/common/time/timesup.cpp create mode 100644 src/VBox/Runtime/common/time/timesupA.asm create mode 100644 src/VBox/Runtime/common/time/timesupA.mac create mode 100644 src/VBox/Runtime/common/time/timesupref.cpp create mode 100644 src/VBox/Runtime/common/time/timesupref.h create mode 100644 src/VBox/Runtime/common/time/timesysalias.cpp create mode 100755 src/VBox/Runtime/common/time/timezoneinfo-gen.py create mode 100644 src/VBox/Runtime/common/time/timezoneinfo.cpp create mode 100644 src/VBox/Runtime/common/vfs/Makefile.kup create mode 100644 src/VBox/Runtime/common/vfs/vfsbase.cpp create mode 100644 src/VBox/Runtime/common/vfs/vfschain.cpp create mode 100644 src/VBox/Runtime/common/vfs/vfsfss2dir.cpp create mode 100644 src/VBox/Runtime/common/vfs/vfsiosmisc.cpp create mode 100644 src/VBox/Runtime/common/vfs/vfsmemory.cpp create mode 100644 src/VBox/Runtime/common/vfs/vfsmisc.cpp create mode 100644 src/VBox/Runtime/common/vfs/vfsmount.cpp create mode 100644 src/VBox/Runtime/common/vfs/vfsmsg.cpp create mode 100644 src/VBox/Runtime/common/vfs/vfsprintf.cpp create mode 100644 src/VBox/Runtime/common/vfs/vfsprogress.cpp create mode 100644 src/VBox/Runtime/common/vfs/vfsreadahead.cpp create mode 100644 src/VBox/Runtime/common/vfs/vfsstddir.cpp create mode 100644 src/VBox/Runtime/common/vfs/vfsstdfile.cpp create mode 100644 src/VBox/Runtime/common/vfs/vfsstdpipe.cpp create mode 100644 src/VBox/Runtime/common/zip/Makefile.kup create mode 100644 src/VBox/Runtime/common/zip/cpiovfs.cpp create mode 100644 src/VBox/Runtime/common/zip/cpiovfsreader.h create mode 100644 src/VBox/Runtime/common/zip/gzipcmd.cpp create mode 100644 src/VBox/Runtime/common/zip/gzipvfs.cpp create mode 100644 src/VBox/Runtime/common/zip/pkzip.cpp create mode 100644 src/VBox/Runtime/common/zip/pkzipvfs.cpp create mode 100644 src/VBox/Runtime/common/zip/tar.h create mode 100644 src/VBox/Runtime/common/zip/tarcmd.cpp create mode 100644 src/VBox/Runtime/common/zip/tarvfs.cpp create mode 100644 src/VBox/Runtime/common/zip/tarvfsreader.h create mode 100644 src/VBox/Runtime/common/zip/tarvfswriter.cpp create mode 100644 src/VBox/Runtime/common/zip/unzipcmd.cpp create mode 100644 src/VBox/Runtime/common/zip/xarvfs.cpp create mode 100644 src/VBox/Runtime/common/zip/zip.cpp (limited to 'src/VBox/Runtime/common') diff --git a/src/VBox/Runtime/common/Makefile.kup b/src/VBox/Runtime/common/Makefile.kup new file mode 100644 index 00000000..e69de29b 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 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include "internal/iprt.h" + +#include +#include + + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#ifdef IN_RING3 +# include +#endif +#include +#include +#include +#include +#include +#include +#include + +#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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms 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 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include + + +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 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#include + +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include + +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + + +/** + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#ifdef IN_RING3 +# include +#endif +#include + +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + + +/** @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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#include + +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + + +/** + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include + +#include + + + +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 +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#include + +#include + + +/********************************************************************************************************************************* +* 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 +#include +#include +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include + +#include + + + +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 +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + +#include + + +/********************************************************************************************************************************* +* 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 +#include +#include +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include + + +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 +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#include + +#include + + +/* + * 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 +#include +#include +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include + +#include + + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include + +#include + + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include + +#include + + +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 +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + +#include + + +/********************************************************************************************************************************* +* 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 +#include +#include +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include + +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include + +#include + + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include + +#include + + +/********************************************************************************************************************************* +* 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 +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#include + +#include + + +/********************************************************************************************************************************* +* 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 +#include +#include +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include + +#include + + +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 +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#include + +#include + + +/********************************************************************************************************************************* +* 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 +#include +#include +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include + +#include + + +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 +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include + +#include + + +/********************************************************************************************************************************* +* 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 +#include +#include +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include + +#include + + +/** + * 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 +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include + +#include + + +/********************************************************************************************************************************* +* 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 +#include +#include +#include + 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: +# = +# 2. The dumpasn1.cfg one: +# ODI = +# = +# 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 . +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include +#include +#include + +/* + * 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 + + +/********************************************************************************************************************************* +* 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 = '. + * It is usually followed by an 'Comment = ', which we ignore, and a + * 'Description = ' 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 + { + /* = */ + 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 [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 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 + + +/** 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 + +#include +#include + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 + + +/** 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 + +#include +#include +#include + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include "internal/iprt.h" + +#include /* for memcpy() */ +#if defined(RT_BIG_ENDIAN) +# include /* 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<>(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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include +#include +#include + + +/** 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 + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include +#include +#include + + +/** 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 + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 + + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include +#include +#include + + +/** 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 + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include + +/** @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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +__FBSDID("$FreeBSD: src/sys/libkern/crc32.c,v 1.2 2003/06/11 05:23:04 obrien Exp $"); + +#include +#include +#else +# include +# 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include + + +/** + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include + + +/** + * @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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 +#ifndef OPENSSL_NO_MD2 +# include +#endif +#include "internal/openssl-post.h" + +#ifndef OPENSSL_NO_MD2 +# define RT_MD2_PRIVATE_CONTEXT +# include + +# include + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 +#include "internal/openssl-post.h" +#if 0 //ndef OPENSSL_NO_MD4 +# include + +# define RT_MD4_PRIVATE_CONTEXT +# include + +# include + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 +#include "internal/openssl-post.h" + +#define RT_MD5_OPENSSL_PRIVATE_CONTEXT +#include + +#include + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 +#include "internal/openssl-post.h" + +#define RT_SHA1_PRIVATE_CONTEXT +#include + +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 +#include "internal/openssl-post.h" + +#define RT_SHA256_PRIVATE_CONTEXT +#include + +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include +#include + +#include "internal/openssl-pre.h" +#include +#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 +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 +#include "internal/openssl-post.h" + +#define RT_SHA512_PRIVATE_CONTEXT +#include + +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#undef __C_specific_handler + +#include +#include + + +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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 +#ifndef IPRT_NOCRT_WITHOUT_FATAL_WRITE +# include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 +#include +#ifndef IPRT_NOCRT_WITHOUT_FATAL_WRITE +# include +#endif + +#include "internal/compiler-vcc.h" +#ifdef IN_RING3 +# include +# 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#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 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +# include +# include +# include + +# include "internal/iprt-openssl.h" +# include "internal/openssl-pre.h" +# include +# 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +# include +# include +# include + +# include "internal/iprt-openssl.h" +# include "internal/openssl-pre.h" +# include +# 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#include + + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include + + + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +# include +# include +# include +# include +# include + +# include "internal/iprt-openssl.h" +# include "internal/openssl-pre.h" +# include +# 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef IPRT_WITH_OPENSSL +# include "internal/iprt-openssl.h" +# include "internal/openssl-pre.h" +# include +# 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 +# include +# include +# include +# include +# include +# include + +# include "internal/iprt-openssl.h" +# include "internal/openssl-pre.h" +# include +# include +# 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#ifdef IPRT_WITH_OPENSSL /* Whole file. */ +# include +# include + +# include "internal/iprt-openssl.h" +# include "internal/openssl-pre.h" +# include +# include +# 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "internal/magics.h" +#include "key-internal.h" + +#ifdef IPRT_WITH_OPENSSL +# include "internal/iprt-openssl.h" +# include "internal/openssl-pre.h" +# include +# include +# include +# 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include + + +/** + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include +#include +#include + +#ifdef IPRT_WITH_OPENSSL +# include "internal/iprt-openssl.h" +# include "internal/magics.h" +# include "internal/openssl-pre.h" +# include +# 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 +# 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#include +#include +#include + + + +/** + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include + +#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 + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include + +#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 + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + +#include "pkcs7-internal.h" + + +static int rtCrPkcs7ContentInfo_CloneExtra(PRTCRPKCS7CONTENTINFO pThis) +{ + pThis->u.pCore = pThis->Content.pEncapsulated; + return VINF_SUCCESS; +} + + +/* + * Generate the code. + */ +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include + +//#include + +#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 + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#include +#include + +#ifdef IPRT_WITH_OPENSSL +# include "internal/iprt-openssl.h" +# include "internal/openssl-pre.h" +# include +# include +# include +# include +# include +# 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef IPRT_WITH_OPENSSL +# include "internal/iprt-openssl.h" +# include "internal/openssl-pre.h" +# include +# include +# include +# 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include + +#include "pkcs8-internal.h" + +/* + * Generate the code. + */ +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + +#include "pkcs8-internal.h" + +/* + * Generate the code. + */ +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include + +#include "pkcs8-internal.h" + +/* + * Generate the code. + */ +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include + +#include "pkcs8-internal.h" + +/* + * Generate the code. + */ +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#include + +#ifdef IPRT_WITH_OPENSSL +# include "internal/iprt-openssl.h" +# include "internal/openssl-pre.h" +# include +# include +# 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include + +#ifdef IPRT_WITH_OPENSSL +# include "internal/iprt-openssl.h" +# include "internal/openssl-pre.h" +# include +# 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +# include +# include +# include +# include +# include +# include +# include + +# include "internal/iprt-openssl.h" +# include "internal/openssl-pre.h" +# include +# 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include + +#ifdef IPRT_WITH_OPENSSL +# include "internal/iprt-openssl.h" +# include "internal/openssl-pre.h" +# include +# 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +# include "internal/openssl-post.h" +# include + +# include +# include + + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include + +#include "rsa-internal.h" + +/* + * Generate the code. + */ +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + +#include "rsa-internal.h" + +/* + * Generate the code. + */ +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include + +#include "rsa-internal.h" + +/* + * Generate the code. + */ +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include + +#include "rsa-internal.h" + +/* + * Generate the code. + */ +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + +#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 + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + +#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 + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + +#include "spc-internal.h" + + +/* + * Generate the standard core code. + */ +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + +#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 "<<>>" 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 + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 + +# include +# include +# include +# include +# include +# include + +# include "internal/magics.h" + +# include "internal/iprt-openssl.h" +# include "internal/openssl-pre.h" +# include +# include +# 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include + +#include +#include + +#ifdef IPRT_WITH_OPENSSL +# include "internal/openssl-pre.h" +# include +# 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + +#include "taf-internal.h" + +/* + * Generate the code. + */ +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include "taf-internal.h" + + +/* + * Generate the code. + */ +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + +#include "taf-internal.h" + +/* + * Generate the code. + */ +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include + +#include "taf-internal.h" + +/* + * Generate the code. + */ +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include "tsp-internal.h" + + +/* + * Generate the code. + */ +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include "tsp-internal.h" + + +/* + * Generate the code. + */ +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include "tsp-internal.h" + + +/* + * Generate the code. + */ +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include "tsp-internal.h" + + +/* + * Generate the code. + */ +#include + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include + +#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 + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include /* critical extension OIDs */ +#include /* PCRTCRPKCS7SETOFCERTS */ +#include + +#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, "", 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#ifdef RT_STRICT +# include +#endif + +#include "x509-internal.h" + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +#ifdef RT_STRICT +static void rtCrX509AlgorithmIdentifier_AssertTableSanityAndMore(void); +#endif + + +/* + * Generate the code. + */ +#include + + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + +#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 + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include + +#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 + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 +#include +#include + +#include +#include +#include + + +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 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + + +/** + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef IPRT_WITH_HTTP +# include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "internal/dbgmod.h" +#include "internal/magics.h" + +#include +#include + + +/********************************************************************************************************************************* +* 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 ""; +} +#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: %#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include "internal/iprt.h" + +#include +#include +#include +#include +#define RTDBGMODCNT_WITH_MEM_CACHE +#ifdef RTDBGMODCNT_WITH_MEM_CACHE +# include +#endif +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "internal/dbgmod.h" + +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#define RTDBGMODDWARF_WITH_MEM_CACHE +#ifdef RTDBGMODDWARF_WITH_MEM_CACHE +# include +#endif +#include +#include +#include +#include +#include +#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] + : ""; + 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, "/", 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) \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 = ""; + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#include +#include + +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) +# include +#else +# error "PORTME" +#endif + +#ifdef RT_OS_WINDOWS +# include +# include +# include +#elif defined(RT_OS_LINUX) || defined(RT_OS_DARWIN) +# include +#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 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + +#include +#include +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include +#include +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include +#include +#include +#include + +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include + +#include +#include + + +/* + * 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 + + +/********************************************************************************************************************************* +* 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> \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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include + +#include + + +/********************************************************************************************************************************* +* 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 . +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under 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 . +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under 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 . + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include + +#ifdef RT_OS_WINDOWS +# include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include +#include +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include +#include + + +#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 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 ""; + } + 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 ""; + } + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 \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 \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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +/********************************************************************************************************************************* +* 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 @@ + + + + + + + + $Date: 2023-01-17 15:15:46 +0100 (Tue, 17 Jan 2023) $ + VISO file format / RTIsoMaker + + + + viso + 8 + + + + + viso + ISO image maker + IPRT + + + + + RTIsoMaker + options + @commands.rsp + filespec + + + + + Description + 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). + + + VISO file format + 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. + + One requirement is that the VISO file must start with one of the + 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. + + 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. + + + + + File specifications and --name-setup + 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. + + 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 specifies the file specification format to use + forthwith. + + The default setup is: + + --name-setup iso+joliet+udf+hfs + + 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. + + Lets look at the following two examples: + + + /docs/readme.txt=/home/user/Documents/product-x-readme.txt + /home/user/Documents/product-x-readme.txt + + In the first case the file '/home/user/Documents/product-x-readme.txt' + is added to the ISO image as '/docs/readme.txt' 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. + + In the second case the file is added to the root under the name + 'product-x-readme.txt' 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. + + Given 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 (==) will be considered omitted. + + A different name in each namespace: + /ISO.TXT=/Joliet.TxT=/UDF.txt=/tmp/iso/real.txt + Specific name in the ISO 9660 namespace, same in the rest: + /ISO.TXT=/OtherNamespaces.TxT=/tmp/iso/real.txt + Omit the file from the ISO 9660 namespace: + =/OtherNamespaces.TxT=/tmp/iso/real.txt + Omit the file from the joliet namespace: + /ISO.TXT==/UDF.TxT=/tmp/iso/real.txt + Use the same filename as the source everywhere: + /tmp/iso/real.txt + + + Using for instance you can add a files/dirs/whatever + to select namespace(s) without the more complicated empty name syntax above. + + When adding directories, you can only control the naming and omitting of the directory + itself, not any recursively added files and directories below it. + + + + + + Options + + + General + + + + + + The output filename. This option is not supported in VISO mode. + + + + + 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, '+' or + '|' 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. + Major namespaces and aliases in parentheses: + + iso (primary, iso9660, iso-9660, primary-iso, iso-primary) + joliet + udf + hfs (hfs-plus) + + Minor namespaces: + + rock: rock ridge on previous major namespace (iso / joliet) + iso-rock: rock ridge extensions on primary ISO 9660 namespace + joliet-rock: rock ridge on joliet namespace (just for fun) + trans-tbl: translation table file on previous major namespace + iso-trans-tbl + joliet-trans-tbl + udf-trans-tbl + hfs-trans-tbl + + + + + + + This is for use following one or more + 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. + + + + + + + + + Open the specified ISO file and use it as source file system until the + corresponding 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 + :iprtvfs: syntax. + + + + + + Pops a of the source file system stack. + + + + + Imports everything on the given ISO file, including boot configuration and + system area (first 16 sectors) content. You can use to omit + namespaces. + + + + + + + Namespaces + + + + + + Sets the ISO level: + + 0: Disable primary ISO namespace. + 1: ISO level 1: Filenames 8.3 format and limited to 4GB - 1. + 2: ISO level 2: 31 char long names and limited to 4GB - 1. + 3: ISO level 3: 31 char long names and support for >=4GB files. (default) + 4: Fictive level used by other tools. Not yet implemented. + + + + + + + + + Enables or disables rock ridge support for the primary ISO 9660 namespace. + The option omits a couple of bits in the root + directory that would make Linux pick rock ridge over joliet. + Default: + + + + + + + + Enables or disable the joliet namespace. This option must precede any file + specifications. + Default: + + + + + + + Set the Joliet UCS support level. This is currently only flagged in the + image but not enforced on the actual path names. + Default level: 3 + + + + + + + + File Attributes + + + + + Enables rational file attribute handling (default): + + Owner ID is set to zero + Group ID is set to zero + Mode is set to 0444 for non-executable files. + Mode is set to 0555 for executable files. + Mode is set to 0555 for directories, preserving stick bits. + + + + + + + Counters and causes attributes to be + recorded exactly as they appear in the source. + + + + + + + Controls the forced file mode mask for rock ridge, UDF and HFS. + + + + + + Controls the forced directory mode mask for rock ridge, UDF and HFS. + + + + + Controls the default mode mask (rock ridge, UDF, HFS) for directories that + are created implicitly. The option overrides this. + + + + + + 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, ra+x, + a+r, or a+rx. + (Support for more complicated mode specifications may be implemented at a later point.) + Note that only namespaces in the current --name-setup are affected. + + + + + + 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. + Note that only namespaces in the current --name-setup are affected. + + + + + --chgrp=group-id:on-iso-file + 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. + Note that only namespaces in the current --name-setup are affected. + + + + + + + + Booting + + + + + + Starts a new El Torito boot entry. + + + + + 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. + + + + + + + Specifies a file on the ISO as the El Torito boot image for the current boot + entry. + + + + + + + + + + Sets the boot image emulation type of the current El Torito boot entry. + + + + + Specify the image load segment for the current El Torito boot entry. + Default: 0x7c0 + + + + + + Specify the image load size in emulated sectors for the current El Torito + boot entry. + Default: 4 (sectors of 512 bytes) + + + + + + Indicates that the current El Torito boot entry isn't bootable. (The BIOS + will allegedly configure the emulation, but not attempt booting.) + + + + + + Write a isolinux/syslinux boot info table into the boot image for the + current El Torito boot entry. + + + + + + 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: + x86, PPC, + Mac, efi + + + + + + + Enters the El Torito boot catalog into the namespaces as a file. The + namespec uses the same format as a 'filespec', but omits the + final source file system name component. + + + + + + + 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. + + + + + + + + String properties (applied to active namespaces only) + + + + + The name of the abstract file in the root dir. + + + + + + Application ID string or root file name. The latter must be prefixed with + an underscore. + + + + + + The name of the bibliographic file in the root dir. + + + + + The name of the copyright file in the root dir. + + + + + + Publisher ID string or root file name. The latter must be prefixed with an + underscore. + + + + + + + Data preparer ID string or root file name. The latter must be prefixed + with an underscore. + + + + + + System ID string. + + + + + + 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 between occurences.) + + + + + + Volume set ID string. + + + + + + + Compatibility: + + + + + Alias for --name-setup iso+joliet+udf+hfs. + + + + + + Allow 31 charater filenames. Just ensure ISO level >= 2 here. + + + + + + Same as and . + + + + + + Same as and . + + + + + + + + VISO Specific: + + + + + + + Used as first option in a VISO file to specify the file UUID and that it is + formatted using bourne-shell argument quoting & escaping style. + + + + + + + Used as first option in a VISO file to specify the file UUID and that it is + formatted using microsoft CRT argument quoting & escaping style. + + + + + + + + + Testing (not applicable to VISO): + + + + + Selects a specific output buffer size for testing virtual image reads. + + + + + Enables randomized buffer size for each virtual image read, using the + current output buffer size () as maximum. + + + + + + Enables verification pass of the image that compares blocks of the given + size in random order from the virtual and output images. + + + + + + + + + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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] \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 + * + * 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 */ + rc = rtFsIsoMakerCmdOptGenericBoot(pOpts, ValueUnion.psz); + break; + + case RTFSISOMAKERCMD_OPT_ELTORITO_ADD_IMAGE: + rc = rtFsIsoMakerCmdOptEltoritoAddImage(pOpts, ValueUnion.psz); + break; + + case 'b': /* --eltorito-boot */ + 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 */ + 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 )"); + + /* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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), ""); + cwcName = 10; + } + } + else if (pFid->fFlags & UDF_FILE_FLAGS_PARENT) + { + wszName[0] = '.'; + wszName[1] = '.'; + cwcName = 2; + } + else + { + RTUtf16CopyAscii(wszName, RT_ELEMENTS(wszName), ""); + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include /* 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 %#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 \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 \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 \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] \n")); + Log2(("XFS: abUuid \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 \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 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/** 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/** + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include +#include +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include +#include +#include +#include + +#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 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 + * + * 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 +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#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 + * + * 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 + * + * 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 + * + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 + * + * 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 +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#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: + + + + + cdhashes + + + hul2SSkDQFRXbGlt3AmCp25MU0Y= + + + N0kvxg0CJBNuZTq135PntAaRczw= + + + + + */ + + /* */ + CHECK_STR_AND_SKIP_OR_RETURN(""); + SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN(); + + /* */ + CHECK_STR_AND_SKIP_OR_RETURN(""); + SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN(); + + /* */ + CHECK_STR_AND_SKIP_OR_RETURN(""); + SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN(); + + /* */ + CHECK_STR_AND_SKIP_OR_RETURN(""); + SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN(); + + /* cdhashes */ + CHECK_STR_AND_SKIP_OR_RETURN("cdhashes"); + SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN(); + + /* */ + CHECK_STR_AND_SKIP_OR_RETURN(""); + SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN(); + + /* + * Repeated: hul2SSkDQFRXbGlt3AmCp25MU0Y= + */ + uint32_t iCodeDir = 0; + for (;;) + { + /* Decode the binary data (base64) and skip it. */ + CHECK_STR_AND_SKIP_OR_RETURN(""); + 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(""); + 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("")) == 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); + } + + /**/ + CHECK_STR_AND_SKIP_OR_RETURN(""); + SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN(); + + /**/ + CHECK_STR_AND_SKIP_OR_RETURN(""); + SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN(); + + /**/ + CHECK_STR_AND_SKIP_OR_RETURN(""); + 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("cdhasheshul2SSkDQFRXbGlt3AmCp25MU0Y=") - 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if !defined(IPRT_WITHOUT_LDR_VERIFY) || !defined(IPRT_WITHOUT_LDR_PAGE_HASHING) +# include +#endif +#ifndef IPRT_WITHOUT_LDR_VERIFY +# include +# include +# include +#endif +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#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 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#ifdef IN_RING3 +# include +# include +# include +# include +#endif +#include +#include +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) +# include +#endif +#include +#include +#include + +#include +#include +#include +#ifdef IN_RING3 +# include +# ifndef IPRT_NO_CRT +# include +# 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include "internal/iprt.h" + +#include +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) /** @todo consider fixing the config instead. */ +# include +#endif +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include + + +/** + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#ifdef IN_RING3 +# include +# include +#endif + +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +/** + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + + +#include +#include +#include +#include +#ifndef IN_RC +# include +#endif +#include +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) +# include +#endif +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 +#include + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 +#include + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include + + +#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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include +#include +#include +#include +#if RTBIGNUM_ELEMENT_BITS == 64 +# include +#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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include + + +#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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) +# include +# include +#endif +#include + + +/********************************************************************************************************************************* +* 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) +# include +# include +#endif +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include + + +/* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include + + +/* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include +#ifdef RT_COMPILER_WITH_128BIT_LONG_DOUBLE +# include +#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 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 +#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 +#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 +#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 +#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 +#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 +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include + +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 +#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 +#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 +#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 +#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 +#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 +#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 +#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 +#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 +#if !defined(_KERNEL) && !defined(_STANDALONE) +#include +#else +#include +#endif +#else /* iprt */ +# include +# include +# 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 +#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 +#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 +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include + +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 +#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 +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include + + +#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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include + + +#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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + + +#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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + + +#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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + + +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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#ifdef IPRT_WITH_ASSERT_STACK +# ifndef IN_RING3 +# error "IPRT_WITH_ASSERT_STACK is only for ring-3 at present." +# endif +# include +#endif +#include +#include +#include +#include +#ifdef IN_RING3 +# include +# ifndef IPRT_NO_CRT +# include +# endif +# ifdef RT_OS_WINDOWS +# include +# 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 : "", + RT_VALID_PTR(pszFile) ? pszFile : "", + 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 : "", + RT_VALID_PTR(pszFile) ? pszFile : "", + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include /* must come before getopt.h */ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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" 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include /* strtod() */ +#include /* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 ""; + if (!RT_VALID_PTR(pThread)) + return ""; + if (pThread->u32Magic != RTTHREADINT_MAGIC) + return ""; + 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 ""; + if (!RT_VALID_PTR(pClass)) + return ""; + if (pClass->u32Magic != RTLOCKVALCLASS_MAGIC) + return ""; + if (!pClass->pszName) + return ""; + 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\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 : ""); + else + RTAssertMsg2Weak("%s [thrd=%s]\n", pszWhat, RT_VALID_PTR(pThreadSelf) ? pThreadSelf->szName : ""); + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#ifdef IN_RING3 +# include +# define RTONCE_USE_CRITSECT_FOR_TERM +#elif defined(IN_RING0) +# include +# define RTONCE_USE_SPINLOCK_FOR_TERM +#else +# define RTONCE_NO_TERM +#endif +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include +#include + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#ifdef IN_RING0 +# include +#endif +#include +#include +#include +#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 : "", + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include + +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 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include /* must come before getopt.h */ +#include "internal/iprt.h" + +#include +#include +#include +#include +#include + + +/** + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#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 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 +#include +#include +#include + + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS +# include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + + +/** + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + + +/** + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include + + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include + + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(RT_OS_WINDOWS) +# include +# include +# include "../../r3/win/internal-r3-win.h" + +#elif defined(RT_OS_OS2) +# define INCL_BASE +# include +# 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 +#include + + +/** + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 +#include +#include +#include + + + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 +#include +#include +#include + + + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 +#include +#include +#include + + + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include + + +/********************************************************************************************************************************* +* 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 +#include +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 +#include +#include +#include + + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include + + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 +#include + + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 +#include + + +/** + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 +#include +#include + + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 +#include +#include +#include + + +/** + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + + +/** + * 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 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) +# include +#endif +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#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 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include + + + +/** + * 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 const *)a_rThat.m_pData); + case kTypeClass_StringMap: return assignCopy(*(RTCRestStringMap 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 const &a_rThat) RT_NOEXCEPT +{ + setNull(); + RTCRestArray *pData = new (std::nothrow) RTCRestArray(); + 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 const &a_rThat) RT_NOEXCEPT +{ + setNull(); + RTCRestStringMap *pData = new (std::nothrow) RTCRestStringMap(); + 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 *pMap = new (std::nothrow) RTCRestStringMap(); + if (pMap) + { + m_pData = pMap; + m_fNullIndicator = false; + return pMap->deserializeFromJson(a_rCursor); + } + break; + } + + case RTJSONVALTYPE_ARRAY: + { + RTCRestArray *pArray = new (std::nothrow) RTCRestArray(); + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include + + +/********************************************************************************************************************************* +* 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"; +} + + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include +#include +#include +#include + + +/** + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include +#include +#include +#include +#include +#include + + + +/** + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include +#include +#include + + +/** + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include +#include + + +/** + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include + + +/** + * 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"; +} + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include + +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include +#include + +#include +#include +#include + + + +/********************************************************************************************************************************* +* 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, ""); + Assert(paEntries[m_iEnumValue - 1].iValue == m_iEnumValue); + return paEntries[m_iEnumValue - 1].pszName; + } + + AssertReturn(m_iEnumValue == 0, ""); + 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 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + +#include +#include +#include + + + +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 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include "internal/iprt.h" + +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#ifdef RT_STRICT +# include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#ifdef RT_STRICT +# include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + + +/** + * 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + + +/** + * 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + + +/** + * 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + + +#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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + + +/** + * 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + + +/** + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include + + + +/********************************************************************************************************************************* +* 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::split(const RTCString &a_rstrSep, SplitMode mode /* = RemoveEmptyParts */) const +{ + RTCList 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 &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 &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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include +#include +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include + + +/** + * @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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + + +/** + * 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + + +/** + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + + +/** + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include "internal/iprt.h" + +#include +#ifdef IN_RING3 +# include +# include +# include +# include +#endif +#include +#include +#include +#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[] = ""; + 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("")); + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include "internal/iprt.h" + +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include "internal/iprt.h" + +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#ifndef RT_NO_EXPORT_SYMBOL +# define RT_NO_EXPORT_SYMBOL /* don't slurp which then again + slurps arch-specific headers defining symbols */ +#endif +#include "internal/iprt.h" + +#include +#include +#include +#include +#ifdef IN_RING3 +# include +# include +# include +#endif +#include +#include +#include +#include +#include +#define STRFORMAT_WITH_X86 +#ifdef STRFORMAT_WITH_X86 +# include +#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 **** ", + 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("")); + 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 = ""; + 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, "<", 4); break; + case '>': cchOutput += pfnOutput(pvArgOutput, ">", 4); break; + case '&': cchOutput += pfnOutput(pvArgOutput, "&", 5); break; + case '\'': cchOutput += pfnOutput(pvArgOutput, "'", 6); break; + case '"': cchOutput += pfnOutput(pvArgOutput, """, 6); break; + case '\n': cchOutput += pfnOutput(pvArgOutput, " ", 5); break; + case '\r': cchOutput += pfnOutput(pvArgOutput, " ", 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 = ""; + 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 = ""; + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include "internal/iprt.h" + +#include +#include +#include +#include +#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("")); + } + + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#ifndef IN_RING0 +# include +#endif +#include +#include +#include +#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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + + +/** + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + + +/** + * 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + + +/** + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + + +/** + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + + +/** + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include + + +/** + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include /* needed for RT_C_IS_DIGIT */ +#include + +#include +#include +#if !defined(_MSC_VER) || !defined(IPRT_NO_CRT) /** @todo fix*/ +# include +#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 +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include /* needed for RT_C_IS_DIGIT */ +#include + + +/********************************************************************************************************************************* +* 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 +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 + +static const uint8_t g_afRTUniFlags0x000000[] = +{ + 0, /* U+000000: */ + 0, /* U+000001: */ + 0, /* U+000002: */ + 0, /* U+000003: */ + 0, /* U+000004: */ + 0, /* U+000005: */ + 0, /* U+000006: */ + 0, /* U+000007: */ + 0, /* U+000008: */ + RTUNI_WSPACE, /* U+000009: */ + RTUNI_WSPACE, /* U+00000a: */ + RTUNI_WSPACE, /* U+00000b: */ + RTUNI_WSPACE, /* U+00000c: */ + RTUNI_WSPACE, /* U+00000d: */ + 0, /* U+00000e: */ + 0, /* U+00000f: */ + 0, /* U+000010: */ + 0, /* U+000011: */ + 0, /* U+000012: */ + 0, /* U+000013: */ + 0, /* U+000014: */ + 0, /* U+000015: */ + 0, /* U+000016: */ + 0, /* U+000017: */ + 0, /* U+000018: */ + 0, /* U+000019: */ + 0, /* U+00001a: */ + 0, /* U+00001b: */ + 0, /* U+00001c: */ + 0, /* U+00001d: */ + 0, /* U+00001e: */ + 0, /* U+00001f: */ + 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: */ + 0, /* U+000080: */ + 0, /* U+000081: */ + 0, /* U+000082: */ + 0, /* U+000083: */ + 0, /* U+000084: */ + RTUNI_WSPACE, /* U+000085: */ + 0, /* U+000086: */ + 0, /* U+000087: */ + 0, /* U+000088: */ + 0, /* U+000089: */ + 0, /* U+00008a: */ + 0, /* U+00008b: */ + 0, /* U+00008c: */ + 0, /* U+00008d: */ + 0, /* U+00008e: */ + 0, /* U+00008f: */ + 0, /* U+000090: */ + 0, /* U+000091: */ + 0, /* U+000092: */ + 0, /* U+000093: */ + 0, /* U+000094: */ + 0, /* U+000095: */ + 0, /* U+000096: */ + 0, /* U+000097: */ + 0, /* U+000098: */ + 0, /* U+000099: */ + 0, /* U+00009a: */ + 0, /* U+00009b: */ + 0, /* U+00009c: */ + 0, /* U+00009d: */ + 0, /* U+00009e: */ + 0, /* U+00009f: */ + 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: */ +}; + +static const uint8_t g_afRTUniFlags0x004db5[] = +{ + RTUNI_ALPHA, /* U+004db5: */ + 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: */ +}; + +static const uint8_t g_afRTUniFlags0x009fcc[] = +{ + RTUNI_ALPHA, /* U+009fcc: */ + 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: */ + 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: */ + 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: */ +}; + +static const uint8_t g_afRTUniFlags0x02a6d6[] = +{ + RTUNI_ALPHA, /* U+02a6d6: */ + 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: */ +}; + +static const uint8_t g_afRTUniFlags0x02b734[] = +{ + RTUNI_ALPHA, /* U+02b734: */ + 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: */ + 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: */ +}; + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 + +static const RTUNICP g_afRTUniLower0x000000[] = +{ + 0x00, /* U+000000: */ + 0x01, /* U+000001: */ + 0x02, /* U+000002: */ + 0x03, /* U+000003: */ + 0x04, /* U+000004: */ + 0x05, /* U+000005: */ + 0x06, /* U+000006: */ + 0x07, /* U+000007: */ + 0x08, /* U+000008: */ + 0x09, /* U+000009: */ + 0x0a, /* U+00000a: */ + 0x0b, /* U+00000b: */ + 0x0c, /* U+00000c: */ + 0x0d, /* U+00000d: */ + 0x0e, /* U+00000e: */ + 0x0f, /* U+00000f: */ + 0x10, /* U+000010: */ + 0x11, /* U+000011: */ + 0x12, /* U+000012: */ + 0x13, /* U+000013: */ + 0x14, /* U+000014: */ + 0x15, /* U+000015: */ + 0x16, /* U+000016: */ + 0x17, /* U+000017: */ + 0x18, /* U+000018: */ + 0x19, /* U+000019: */ + 0x1a, /* U+00001a: */ + 0x1b, /* U+00001b: */ + 0x1c, /* U+00001c: */ + 0x1d, /* U+00001d: */ + 0x1e, /* U+00001e: */ + 0x1f, /* U+00001f: */ + 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: */ + 0x80, /* U+000080: */ + 0x81, /* U+000081: */ + 0x82, /* U+000082: */ + 0x83, /* U+000083: */ + 0x84, /* U+000084: */ + 0x85, /* U+000085: */ + 0x86, /* U+000086: */ + 0x87, /* U+000087: */ + 0x88, /* U+000088: */ + 0x89, /* U+000089: */ + 0x8a, /* U+00008a: */ + 0x8b, /* U+00008b: */ + 0x8c, /* U+00008c: */ + 0x8d, /* U+00008d: */ + 0x8e, /* U+00008e: */ + 0x8f, /* U+00008f: */ + 0x90, /* U+000090: */ + 0x91, /* U+000091: */ + 0x92, /* U+000092: */ + 0x93, /* U+000093: */ + 0x94, /* U+000094: */ + 0x95, /* U+000095: */ + 0x96, /* U+000096: */ + 0x97, /* U+000097: */ + 0x98, /* U+000098: */ + 0x99, /* U+000099: */ + 0x9a, /* U+00009a: */ + 0x9b, /* U+00009b: */ + 0x9c, /* U+00009c: */ + 0x9d, /* U+00009d: */ + 0x9e, /* U+00009e: */ + 0x9f, /* U+00009f: */ + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 + +static const RTUNICP g_afRTUniUpper0x000000[] = +{ + 0x00, /* U+000000: */ + 0x01, /* U+000001: */ + 0x02, /* U+000002: */ + 0x03, /* U+000003: */ + 0x04, /* U+000004: */ + 0x05, /* U+000005: */ + 0x06, /* U+000006: */ + 0x07, /* U+000007: */ + 0x08, /* U+000008: */ + 0x09, /* U+000009: */ + 0x0a, /* U+00000a: */ + 0x0b, /* U+00000b: */ + 0x0c, /* U+00000c: */ + 0x0d, /* U+00000d: */ + 0x0e, /* U+00000e: */ + 0x0f, /* U+00000f: */ + 0x10, /* U+000010: */ + 0x11, /* U+000011: */ + 0x12, /* U+000012: */ + 0x13, /* U+000013: */ + 0x14, /* U+000014: */ + 0x15, /* U+000015: */ + 0x16, /* U+000016: */ + 0x17, /* U+000017: */ + 0x18, /* U+000018: */ + 0x19, /* U+000019: */ + 0x1a, /* U+00001a: */ + 0x1b, /* U+00001b: */ + 0x1c, /* U+00001c: */ + 0x1d, /* U+00001d: */ + 0x1e, /* U+00001e: */ + 0x1f, /* U+00001f: */ + 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: */ + 0x80, /* U+000080: */ + 0x81, /* U+000081: */ + 0x82, /* U+000082: */ + 0x83, /* U+000083: */ + 0x84, /* U+000084: */ + 0x85, /* U+000085: */ + 0x86, /* U+000086: */ + 0x87, /* U+000087: */ + 0x88, /* U+000088: */ + 0x89, /* U+000089: */ + 0x8a, /* U+00008a: */ + 0x8b, /* U+00008b: */ + 0x8c, /* U+00008c: */ + 0x8d, /* U+00008d: */ + 0x8e, /* U+00008e: */ + 0x8f, /* U+00008f: */ + 0x90, /* U+000090: */ + 0x91, /* U+000091: */ + 0x92, /* U+000092: */ + 0x93, /* U+000093: */ + 0x94, /* U+000094: */ + 0x95, /* U+000095: */ + 0x96, /* U+000096: */ + 0x97, /* U+000097: */ + 0x98, /* U+000098: */ + 0x99, /* U+000099: */ + 0x9a, /* U+00009a: */ + 0x9b, /* U+00009b: */ + 0x9c, /* U+00009c: */ + 0x9d, /* U+00009d: */ + 0x9e, /* U+00009e: */ + 0x9f, /* U+00009f: */ + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include + +#include +#include +#include +#ifdef _MSC_VER +# include +#else +# include +#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 .\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 \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 ] [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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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() + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include +#include + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include +#include + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include +#include + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include +#include + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include +#include + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include +#include + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include +#include + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include +#include + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include +#include + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include +#include + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include +#include + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include +#include + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include +#include + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include +#include + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include +#include + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include +#include + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include +#include + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include +#include + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include +#include + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include +#include + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include +#include + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include +#include + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include +#include + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + 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 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include "internal/iprt.h" + +#include +#include +#include +#include +#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 +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "internal/iprt.h" + +#include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include "internal/iprt.h" + +#include +#include +#include +#if !defined(IN_GUEST) && !defined(RT_NO_GIP) +# include +# include +# include +# include +#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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#ifdef IN_RC +# include +# include +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#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 . + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under 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'); + # + 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: "); + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include "internal/iprt.h" + +#include + + +/********************************************************************************************************************************* +* 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 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include +#include +#ifndef RTVFSFSS2DIR_USE_DIR +# include +#endif +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include + +#include +#include +#include + + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#include +#include +#include + + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include + +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include + + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + +#include +#include + + +/** 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include + +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include + +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms 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 +#include + +#include +#include +#include +#include +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include + +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include + +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 + + +/** + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 + +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#include + +/** + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 , --directory (-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 , --file (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 (-A, -c, -d, -r, -u, -x)\n" + " Set the owner of extracted and archived files to the user specified.\n" + " --group (-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 (-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 (-A, -c, -d, -r, -u)\n" + " Directory prefix to give the members added to the archive.\n" + " --file-mode-and-mask (-A, -c, -d, -r, -u, -x)\n" + " Restrict the access mode of regular and special files.\n" + " --file-mode-or-mask (-A, -c, -d, -r, -u, -x)\n" + " Include the given access mode for regular and special files.\n" + " --dir-mode-and-mask (-A, -c, -d, -r, -u, -x)\n" + " Restrict the access mode of directories.\n" + " --dir-mode-or-mask (-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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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(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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under 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 +#ifdef RTZIP_USE_BZLIB +# include +#endif +#ifdef RTZIP_USE_ZLIB +# include +#endif +#ifdef RTZIP_USE_LZF + RT_C_DECLS_BEGIN +# include + RT_C_DECLS_END +# include +#endif +#ifdef RTZIP_USE_LZJB +# include "lzjb.h" +#endif +#ifdef RTZIP_USE_LZO +# include +#endif + +#include +#include "internal/iprt.h" + +/*#include */ +#include +#include +#include +#include +#include + +#ifndef IPRT_NO_CRT +# include +#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); + -- cgit v1.2.3